diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index b9c6f314..6a4aa284 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -13,6 +13,17 @@ updates:
versions: [ "[6.0,)" ]
- dependency-name: "org.junit.platform:*"
versions: [ "[6.0,)" ]
+ - package-ecosystem: "maven"
+ directory: "/java-amqp"
+ schedule:
+ interval: "daily"
+ open-pull-requests-limit: 20
+ target-branch: "main"
+ ignore:
+ - dependency-name: "org.junit.jupiter:*"
+ versions: [ "[6.0,)" ]
+ - dependency-name: "org.junit.platform:*"
+ versions: [ "[6.0,)" ]
- package-ecosystem: "maven"
directory: "/java-stream-mvn"
schedule:
diff --git a/.gitignore b/.gitignore
index 42ae1774..35e5b18c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,5 @@ target/
.classpath
.project
.settings
+
+/.cursor/plans
diff --git a/dotnet-amqp/.gitattributes b/dotnet-amqp/.gitattributes
new file mode 100644
index 00000000..27ab416b
--- /dev/null
+++ b/dotnet-amqp/.gitattributes
@@ -0,0 +1,13 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Auto detect text files and perform LF normalization
+*.cs text=auto eol=lf
+*.txt text=auto
+
+# Declare files that will always have CRLF line endings on checkout.
+*.sln text eol=crlf
+*.csproj text eol=crlf
+
+# Custom for Visual Studio
+*.cs diff=csharp
diff --git a/dotnet-amqp/.gitignore b/dotnet-amqp/.gitignore
new file mode 100644
index 00000000..4cbb9f26
--- /dev/null
+++ b/dotnet-amqp/.gitignore
@@ -0,0 +1,6 @@
+*.dll
+*.exe
+*.lock.json
+packages/
+bin/
+obj/
diff --git a/dotnet-amqp/EmitLog/EmitLog.csproj b/dotnet-amqp/EmitLog/EmitLog.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/EmitLog/EmitLog.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/EmitLog/Program.cs b/dotnet-amqp/EmitLog/Program.cs
new file mode 100644
index 00000000..78e95436
--- /dev/null
+++ b/dotnet-amqp/EmitLog/Program.cs
@@ -0,0 +1,58 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string exchangeName = "logs";
+
+string message = args.Length < 1 ? "info: Hello World!" : string.Join(" ", args);
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-emitlog")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("fanout");
+ await exchangeSpec.DeclareAsync();
+
+ IPublisher publisher = await connection.PublisherBuilder().Exchange(exchangeName).BuildAsync();
+ try
+ {
+ var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message));
+ PublishResult pr = await publisher.PublishAsync(amqpMessage);
+ switch (pr.Outcome.State)
+ {
+ case OutcomeState.Accepted:
+ break;
+ case OutcomeState.Released:
+ Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}");
+ Environment.Exit(1);
+ break;
+ case OutcomeState.Rejected:
+ Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}");
+ Environment.Exit(1);
+ break;
+ default:
+ Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}");
+ Environment.Exit(1);
+ break;
+ }
+
+ Console.WriteLine($" [x] Sent '{message}'");
+ }
+ finally
+ {
+ await publisher.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
diff --git a/dotnet-amqp/EmitLogDirect/EmitLogDirect.csproj b/dotnet-amqp/EmitLogDirect/EmitLogDirect.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/EmitLogDirect/EmitLogDirect.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/EmitLogDirect/Program.cs b/dotnet-amqp/EmitLogDirect/Program.cs
new file mode 100644
index 00000000..004036d0
--- /dev/null
+++ b/dotnet-amqp/EmitLogDirect/Program.cs
@@ -0,0 +1,71 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string exchangeName = "logs_direct";
+
+string severity = GetSeverity(args);
+string message = GetMessage(args);
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-emitlogdirect")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("direct");
+ await exchangeSpec.DeclareAsync();
+
+ IPublisher publisher = await connection.PublisherBuilder().Exchange(exchangeName).Key(severity).BuildAsync();
+ try
+ {
+ var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message));
+ PublishResult pr = await publisher.PublishAsync(amqpMessage);
+ switch (pr.Outcome.State)
+ {
+ case OutcomeState.Accepted:
+ break;
+ case OutcomeState.Released:
+ Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}");
+ Environment.Exit(1);
+ break;
+ case OutcomeState.Rejected:
+ Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}");
+ Environment.Exit(1);
+ break;
+ default:
+ Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}");
+ Environment.Exit(1);
+ break;
+ }
+
+ Console.WriteLine($" [x] Sent '{severity}':'{message}'");
+ }
+ finally
+ {
+ await publisher.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
+
+static string GetSeverity(string[] strings) => strings.Length < 1 ? "info" : strings[0];
+
+static string GetMessage(string[] strings)
+{
+ if (strings.Length < 2)
+ {
+ return "Hello World!";
+ }
+
+ return string.Join(" ", strings.Skip(1));
+}
diff --git a/dotnet-amqp/EmitLogTopic/EmitLogTopic.csproj b/dotnet-amqp/EmitLogTopic/EmitLogTopic.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/EmitLogTopic/EmitLogTopic.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/EmitLogTopic/Program.cs b/dotnet-amqp/EmitLogTopic/Program.cs
new file mode 100644
index 00000000..388717e9
--- /dev/null
+++ b/dotnet-amqp/EmitLogTopic/Program.cs
@@ -0,0 +1,71 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string exchangeName = "logs_topic";
+
+string routingKey = GetRouting(args);
+string message = GetMessage(args);
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-emitlogtopic")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("topic");
+ await exchangeSpec.DeclareAsync();
+
+ IPublisher publisher = await connection.PublisherBuilder().Exchange(exchangeName).Key(routingKey).BuildAsync();
+ try
+ {
+ var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message));
+ PublishResult pr = await publisher.PublishAsync(amqpMessage);
+ switch (pr.Outcome.State)
+ {
+ case OutcomeState.Accepted:
+ break;
+ case OutcomeState.Released:
+ Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}");
+ Environment.Exit(1);
+ break;
+ case OutcomeState.Rejected:
+ Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}");
+ Environment.Exit(1);
+ break;
+ default:
+ Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}");
+ Environment.Exit(1);
+ break;
+ }
+
+ Console.WriteLine($" [x] Sent '{routingKey}':'{message}'");
+ }
+ finally
+ {
+ await publisher.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
+
+static string GetRouting(string[] strings) => strings.Length < 1 ? "anonymous.info" : strings[0];
+
+static string GetMessage(string[] strings)
+{
+ if (strings.Length < 2)
+ {
+ return "Hello World!";
+ }
+
+ return string.Join(" ", strings.Skip(1));
+}
diff --git a/dotnet-amqp/NewTask/NewTask.csproj b/dotnet-amqp/NewTask/NewTask.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/NewTask/NewTask.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/NewTask/Program.cs b/dotnet-amqp/NewTask/Program.cs
new file mode 100644
index 00000000..62b110a5
--- /dev/null
+++ b/dotnet-amqp/NewTask/Program.cs
@@ -0,0 +1,58 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string taskQueueName = "task_queue";
+
+string message = args.Length > 0 ? string.Join(" ", args) : "Hello World!";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-newtask")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IQueueSpecification queueSpec = management.Queue(taskQueueName).Type(QueueType.QUORUM);
+ await queueSpec.DeclareAsync();
+
+ IPublisher publisher = await connection.PublisherBuilder().Queue(taskQueueName).BuildAsync();
+ try
+ {
+ var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message));
+ PublishResult pr = await publisher.PublishAsync(amqpMessage);
+ switch (pr.Outcome.State)
+ {
+ case OutcomeState.Accepted:
+ break;
+ case OutcomeState.Released:
+ Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}");
+ Environment.Exit(1);
+ break;
+ case OutcomeState.Rejected:
+ Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}");
+ Environment.Exit(1);
+ break;
+ default:
+ Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}");
+ Environment.Exit(1);
+ break;
+ }
+
+ Console.WriteLine($" [x] Sent '{message}'");
+ }
+ finally
+ {
+ await publisher.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
diff --git a/dotnet-amqp/PublisherConfirms/Program.cs b/dotnet-amqp/PublisherConfirms/Program.cs
new file mode 100644
index 00000000..b7d1aa57
--- /dev/null
+++ b/dotnet-amqp/PublisherConfirms/Program.cs
@@ -0,0 +1,76 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-publisherconfirms")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ string queueName = Guid.NewGuid().ToString();
+ IQueueSpecification queueSpec = management.Queue(queueName).Exclusive(true).AutoDelete(true);
+ await queueSpec.DeclareAsync();
+
+ IConsumer consumer = await connection.ConsumerBuilder()
+ .Queue(queueName)
+ .MessageHandler((ctx, message) =>
+ {
+ Console.WriteLine($"Received a message: {message.BodyAsString()}");
+ ctx.Accept();
+ return Task.CompletedTask;
+ })
+ .BuildAndStartAsync();
+
+ try
+ {
+ await Task.Delay(300);
+
+ IPublisher publisher = await connection.PublisherBuilder().Queue(queueName).BuildAsync();
+ try
+ {
+ const string text = "hello";
+ PublishResult pr = await publisher.PublishAsync(new AmqpMessage(Encoding.UTF8.GetBytes(text)));
+ switch (pr.Outcome.State)
+ {
+ case OutcomeState.Accepted:
+ Console.WriteLine($" Accepted Message: {pr.Message.BodyAsString()} confirmed");
+ break;
+ case OutcomeState.Released:
+ Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}");
+ Environment.Exit(1);
+ break;
+ case OutcomeState.Rejected:
+ Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}");
+ Environment.Exit(1);
+ break;
+ default:
+ Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}");
+ Environment.Exit(1);
+ break;
+ }
+
+ await Task.Delay(500);
+ }
+ finally
+ {
+ await publisher.CloseAsync();
+ }
+ }
+ finally
+ {
+ await consumer.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
diff --git a/dotnet-amqp/PublisherConfirms/PublisherConfirms.csproj b/dotnet-amqp/PublisherConfirms/PublisherConfirms.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/PublisherConfirms/PublisherConfirms.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/README.md b/dotnet-amqp/README.md
new file mode 100644
index 00000000..f4aa527e
--- /dev/null
+++ b/dotnet-amqp/README.md
@@ -0,0 +1,96 @@
+# Dotnet C# code for RabbitMQ tutorials (AMQP 1.0)
+
+Here you can find C# examples from the [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html), using the [RabbitMQ AMQP 1.0 .NET client](https://github.com/rabbitmq/rabbitmq-amqp-dotnet-client) (`RabbitMQ.AMQP.Client` on NuGet) for RabbitMQ 4.x.
+
+You need a RabbitMQ node running locally. The client requires **RabbitMQ 4.0 or newer** with AMQP 1.0 on port **5672**. The examples use the default `guest` user. See the [AMQP 1.0 client libraries overview](https://www.rabbitmq.com/client-libraries/amqp-client-libraries) and [AMQP in RabbitMQ](https://www.rabbitmq.com/docs/amqp).
+
+There is a solution file for Visual Studio 2022 (`dotnet-amqp.sln`). From this directory, use `dotnet run -f net8.0 --project` as below.
+
+End-to-end smoke tests (broker on `localhost`, management plugin used to reset `hello` / `task_queue` when needed):
+
+```bash
+./test-tutorials.sh
+```
+
+## Requirements
+
+- [.NET 8 or .NET 10 SDK](https://dotnet.microsoft.com/download) (projects target `net8.0` and `net10.0`; use `-f net10.0` instead of `-f net8.0` in the commands below if you prefer)
+- NuGet package: `RabbitMQ.AMQP.Client` (see each `.csproj`; restore runs automatically with `dotnet run` or `dotnet build`)
+
+## Code
+
+Run each example from the `dotnet-amqp` directory.
+
+#### Tutorial one: "Hello World!"
+
+```bash
+dotnet run -f net8.0 --project Receive/Receive.csproj
+dotnet run -f net8.0 --project Send/Send.csproj
+```
+
+#### Tutorial two: Work Queues
+
+```bash
+dotnet run -f net8.0 --project Worker/Worker.csproj
+dotnet run -f net8.0 --project Worker/Worker.csproj
+dotnet run -f net8.0 --project NewTask/NewTask.csproj "First Message"
+dotnet run -f net8.0 --project NewTask/NewTask.csproj "Second Message"
+dotnet run -f net8.0 --project NewTask/NewTask.csproj "Third Message"
+dotnet run -f net8.0 --project NewTask/NewTask.csproj "Fourth Message"
+dotnet run -f net8.0 --project NewTask/NewTask.csproj "Fifth Message"
+```
+
+#### Tutorial three: Publish/Subscribe
+
+```bash
+dotnet run -f net8.0 --project ReceiveLogs/ReceiveLogs.csproj
+dotnet run -f net8.0 --project ReceiveLogs/ReceiveLogs.csproj
+dotnet run -f net8.0 --project EmitLog/EmitLog.csproj
+```
+
+#### Tutorial four: Routing
+
+```bash
+dotnet run -f net8.0 --project ReceiveLogsDirect/ReceiveLogsDirect.csproj warning error
+dotnet run -f net8.0 --project ReceiveLogsDirect/ReceiveLogsDirect.csproj info warning error
+dotnet run -f net8.0 --project EmitLogDirect/EmitLogDirect.csproj info "Run. Run. Or it will explode."
+dotnet run -f net8.0 --project EmitLogDirect/EmitLogDirect.csproj warning "Run. Run. Or it will explode."
+dotnet run -f net8.0 --project EmitLogDirect/EmitLogDirect.csproj error "Run. Run. Or it will explode."
+```
+
+#### Tutorial five: Topics
+
+```bash
+dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "#"
+dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "kern.*"
+dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "*.critical"
+dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "kern.*" "*.critical"
+dotnet run -f net8.0 --project EmitLogTopic/EmitLogTopic.csproj kern.critical "A critical kernel error"
+```
+
+#### Tutorial six: RPC
+
+```bash
+dotnet run -f net8.0 --project RPCServer/RPCServer.csproj
+dotnet run -f net8.0 --project RPCClient/RPCClient.csproj
+```
+
+The client requests Fibonacci numbers for `0` through `31`, matching the Java AMQP 1.0 tutorial client.
+
+#### Publisher confirms (AMQP 1.0 publish outcomes)
+
+```bash
+dotnet run -f net8.0 --project PublisherConfirms/PublisherConfirms.csproj
+```
+
+#### AMQP 1.0 Direct Reply-To RPC
+
+On RabbitMQ **4.2 and newer**, the .NET `Requester` uses [Direct Reply-To](https://www.rabbitmq.com/docs/direct-reply-to) when no explicit reply queue is set (see the client’s requester implementation). Older brokers fall back to a temporary reply queue.
+
+```bash
+dotnet run -f net8.0 --project RpcAmqp10/RpcAmqp10.csproj
+```
+
+This sample runs a responder and a requester in one process; press CTRL+C to exit.
+
+To learn more, see the [API documentation](https://rabbitmq.github.io/rabbitmq-amqp-dotnet-client/api/RabbitMQ.AMQP.Client.html).
diff --git a/dotnet-amqp/RPCClient/Program.cs b/dotnet-amqp/RPCClient/Program.cs
new file mode 100644
index 00000000..0825cdde
--- /dev/null
+++ b/dotnet-amqp/RPCClient/Program.cs
@@ -0,0 +1,44 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-rpcclient")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IRequester requester = await connection.RequesterBuilder()
+ .RequestAddress()
+ .Queue("rpc_queue")
+ .Requester()
+ .BuildAsync();
+
+ try
+ {
+ for (int i = 0; i < 32; i++)
+ {
+ string iStr = i.ToString();
+ Console.WriteLine($" [x] Requesting fib({iStr})");
+ IMessage request = new AmqpMessage(Encoding.UTF8.GetBytes(iStr));
+ IMessage reply = await requester.PublishAsync(request);
+ string response = reply.BodyAsString();
+ Console.WriteLine($" [.] Got '{response}'");
+ }
+ }
+ finally
+ {
+ await requester.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
diff --git a/dotnet-amqp/RPCClient/RPCClient.csproj b/dotnet-amqp/RPCClient/RPCClient.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/RPCClient/RPCClient.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/RPCServer/Program.cs b/dotnet-amqp/RPCServer/Program.cs
new file mode 100644
index 00000000..bac2b135
--- /dev/null
+++ b/dotnet-amqp/RPCServer/Program.cs
@@ -0,0 +1,83 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string rpcQueueName = "rpc_queue";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-rpcserver")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IQueueSpecification queueSpec = management.Queue(rpcQueueName).Type(QueueType.QUORUM);
+ await queueSpec.DeclareAsync();
+ await queueSpec.PurgeAsync();
+
+ Console.WriteLine(" [x] Awaiting RPC requests");
+
+ IResponder responder = await connection.ResponderBuilder()
+ .RequestQueue(rpcQueueName)
+ .Handler((ctx, request) =>
+ {
+ string response = "";
+ try
+ {
+ string message = request.BodyAsString();
+ int n = int.Parse(message);
+ Console.WriteLine($" [.] fib({message})");
+ response += Fib(n);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($" [.] {e.Message}");
+ }
+
+ return Task.FromResult(ctx.Message(Encoding.UTF8.GetBytes(response)));
+ })
+ .BuildAsync();
+
+ try
+ {
+ using var cts = new CancellationTokenSource();
+ Console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ cts.Cancel();
+ };
+ await Task.Delay(Timeout.Infinite, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ finally
+ {
+ await responder.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
+
+static int Fib(int n)
+{
+ if (n == 0)
+ {
+ return 0;
+ }
+
+ if (n == 1)
+ {
+ return 1;
+ }
+
+ return Fib(n - 1) + Fib(n - 2);
+}
diff --git a/dotnet-amqp/RPCServer/RPCServer.csproj b/dotnet-amqp/RPCServer/RPCServer.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/RPCServer/RPCServer.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/Receive/Program.cs b/dotnet-amqp/Receive/Program.cs
new file mode 100644
index 00000000..c0012dd0
--- /dev/null
+++ b/dotnet-amqp/Receive/Program.cs
@@ -0,0 +1,53 @@
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-receive")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IQueueSpecification queueSpec = management.Queue("hello").Type(QueueType.QUORUM);
+ await queueSpec.DeclareAsync();
+
+ IConsumer consumer = await connection.ConsumerBuilder()
+ .Queue("hello")
+ .MessageHandler((ctx, message) =>
+ {
+ Console.WriteLine($"Received a message: {message.BodyAsString()}");
+ ctx.Accept();
+ return Task.CompletedTask;
+ })
+ .BuildAndStartAsync();
+
+ try
+ {
+ Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C");
+ using var cts = new CancellationTokenSource();
+ Console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ cts.Cancel();
+ };
+ await Task.Delay(Timeout.Infinite, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ finally
+ {
+ await consumer.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
diff --git a/dotnet-amqp/Receive/Receive.csproj b/dotnet-amqp/Receive/Receive.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/Receive/Receive.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/ReceiveLogs/Program.cs b/dotnet-amqp/ReceiveLogs/Program.cs
new file mode 100644
index 00000000..60d1f0b2
--- /dev/null
+++ b/dotnet-amqp/ReceiveLogs/Program.cs
@@ -0,0 +1,65 @@
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string exchangeName = "logs";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-receivelogs")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("fanout");
+ await exchangeSpec.DeclareAsync();
+
+ IQueueSpecification tempQueue = management.Queue().Exclusive(true).AutoDelete(true);
+ IQueueInfo queueInfo = await tempQueue.DeclareAsync();
+ string queueName = queueInfo.Name();
+
+ IBindingSpecification binding = management.Binding()
+ .SourceExchange(exchangeSpec)
+ .DestinationQueue(queueName)
+ .Key(string.Empty);
+ await binding.BindAsync();
+
+ IConsumer consumer = await connection.ConsumerBuilder()
+ .Queue(queueName)
+ .MessageHandler((ctx, message) =>
+ {
+ string body = message.BodyAsString();
+ Console.WriteLine($" [x] Received '{body}'");
+ ctx.Accept();
+ return Task.CompletedTask;
+ })
+ .BuildAndStartAsync();
+
+ try
+ {
+ Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C");
+ using var cts = new CancellationTokenSource();
+ Console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ cts.Cancel();
+ };
+ await Task.Delay(Timeout.Infinite, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ finally
+ {
+ await consumer.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
diff --git a/dotnet-amqp/ReceiveLogs/ReceiveLogs.csproj b/dotnet-amqp/ReceiveLogs/ReceiveLogs.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/ReceiveLogs/ReceiveLogs.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/ReceiveLogsDirect/Program.cs b/dotnet-amqp/ReceiveLogsDirect/Program.cs
new file mode 100644
index 00000000..fbafc3cb
--- /dev/null
+++ b/dotnet-amqp/ReceiveLogsDirect/Program.cs
@@ -0,0 +1,86 @@
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string exchangeName = "logs_direct";
+
+if (args.Length < 1)
+{
+ Console.Error.WriteLine("Usage: ReceiveLogsDirect [info] [warning] [error]");
+ Environment.Exit(1);
+}
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-receivelogsdirect")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("direct");
+ await exchangeSpec.DeclareAsync();
+
+ IQueueSpecification tempQueue = management.Queue().Exclusive(true).AutoDelete(true);
+ IQueueInfo queueInfo = await tempQueue.DeclareAsync();
+ string queueName = queueInfo.Name();
+
+ foreach (string severity in args)
+ {
+ IBindingSpecification binding = management.Binding()
+ .SourceExchange(exchangeSpec)
+ .DestinationQueue(queueName)
+ .Key(severity);
+ await binding.BindAsync();
+ }
+
+ IConsumer consumer = await connection.ConsumerBuilder()
+ .Queue(queueName)
+ .MessageHandler((ctx, message) =>
+ {
+ string body = message.BodyAsString();
+ string routingKey = RoutingKey(message);
+ Console.WriteLine($" [x] Received '{routingKey}':'{body}'");
+ ctx.Accept();
+ return Task.CompletedTask;
+ })
+ .BuildAndStartAsync();
+
+ try
+ {
+ Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C");
+ using var cts = new CancellationTokenSource();
+ Console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ cts.Cancel();
+ };
+ await Task.Delay(Timeout.Infinite, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ finally
+ {
+ await consumer.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
+
+static string RoutingKey(IMessage message)
+{
+ object? rk = message.Annotation("x-routing-key");
+ if (rk != null)
+ {
+ return rk.ToString() ?? "";
+ }
+
+ return message.Subject() ?? "";
+}
diff --git a/dotnet-amqp/ReceiveLogsDirect/ReceiveLogsDirect.csproj b/dotnet-amqp/ReceiveLogsDirect/ReceiveLogsDirect.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/ReceiveLogsDirect/ReceiveLogsDirect.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/ReceiveLogsTopic/Program.cs b/dotnet-amqp/ReceiveLogsTopic/Program.cs
new file mode 100644
index 00000000..7a033a4b
--- /dev/null
+++ b/dotnet-amqp/ReceiveLogsTopic/Program.cs
@@ -0,0 +1,86 @@
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string exchangeName = "logs_topic";
+
+if (args.Length < 1)
+{
+ Console.Error.WriteLine("Usage: ReceiveLogsTopic [binding_key]...");
+ Environment.Exit(1);
+}
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-receivelogstopic")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("topic");
+ await exchangeSpec.DeclareAsync();
+
+ IQueueSpecification tempQueue = management.Queue().Exclusive(true).AutoDelete(true);
+ IQueueInfo queueInfo = await tempQueue.DeclareAsync();
+ string queueName = queueInfo.Name();
+
+ foreach (string bindingKey in args)
+ {
+ IBindingSpecification binding = management.Binding()
+ .SourceExchange(exchangeSpec)
+ .DestinationQueue(queueName)
+ .Key(bindingKey);
+ await binding.BindAsync();
+ }
+
+ IConsumer consumer = await connection.ConsumerBuilder()
+ .Queue(queueName)
+ .MessageHandler((ctx, message) =>
+ {
+ string body = message.BodyAsString();
+ string routingKey = RoutingKey(message);
+ Console.WriteLine($" [x] Received '{routingKey}':'{body}'");
+ ctx.Accept();
+ return Task.CompletedTask;
+ })
+ .BuildAndStartAsync();
+
+ try
+ {
+ Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C");
+ using var cts = new CancellationTokenSource();
+ Console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ cts.Cancel();
+ };
+ await Task.Delay(Timeout.Infinite, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ finally
+ {
+ await consumer.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
+
+static string RoutingKey(IMessage message)
+{
+ object? rk = message.Annotation("x-routing-key");
+ if (rk != null)
+ {
+ return rk.ToString() ?? "";
+ }
+
+ return message.Subject() ?? "";
+}
diff --git a/dotnet-amqp/ReceiveLogsTopic/ReceiveLogsTopic.csproj b/dotnet-amqp/ReceiveLogsTopic/ReceiveLogsTopic.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/ReceiveLogsTopic/ReceiveLogsTopic.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/RpcAmqp10/Program.cs b/dotnet-amqp/RpcAmqp10/Program.cs
new file mode 100644
index 00000000..48fc7b21
--- /dev/null
+++ b/dotnet-amqp/RpcAmqp10/Program.cs
@@ -0,0 +1,162 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string requestQueue = "rpc-requests";
+
+using var cts = new CancellationTokenSource();
+Console.CancelKeyPress += (_, e) =>
+{
+ e.Cancel = true;
+ cts.Cancel();
+};
+
+Console.WriteLine("Starting Direct Reply-To RPC example (RabbitMQ 4.2+ uses Direct Reply-To for the requester when no reply queue is set).");
+Console.WriteLine("Ensure RabbitMQ is running on localhost:5672.");
+
+Task serverTask = RunServerAsync(cts.Token);
+await Task.Delay(500, CancellationToken.None);
+
+Task clientTask = RunClientAsync(cts.Token);
+
+try
+{
+ await Task.Delay(Timeout.Infinite, cts.Token);
+}
+catch (OperationCanceledException)
+{
+}
+
+Console.WriteLine("Application shutting down...");
+await Task.WhenAll(
+ AwaitCancelled(serverTask),
+ AwaitCancelled(clientTask));
+
+static async Task AwaitCancelled(Task task)
+{
+ try
+ {
+ await task;
+ }
+ catch (OperationCanceledException)
+ {
+ }
+}
+
+async Task RunServerAsync(CancellationToken cancellationToken)
+{
+ ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("rpc-amqp10-server")
+ .Build();
+
+ IEnvironment environment = AmqpEnvironment.Create(settings);
+ IConnection connection = await environment.CreateConnectionAsync();
+
+ try
+ {
+ IManagement management = connection.Management();
+ IQueueSpecification queueSpec = management.Queue(requestQueue).Type(QueueType.QUORUM);
+ await queueSpec.DeclareAsync();
+
+ IResponder responder = await connection.ResponderBuilder()
+ .RequestQueue(requestQueue)
+ .Handler((ctx, request) =>
+ {
+ string payload = request.BodyAsString();
+ object? mid = request.MessageId();
+ if (mid != null)
+ {
+ Console.WriteLine($"RPC Server: Received {payload} request (ID: {mid})");
+ }
+ else
+ {
+ Console.WriteLine($"RPC Server: Received {payload} request");
+ }
+
+ return Task.FromResult(ctx.Message(Encoding.UTF8.GetBytes("pong")));
+ })
+ .BuildAsync();
+
+ try
+ {
+ Console.WriteLine("RPC Server: Started and listening for requests...");
+ await Task.Delay(Timeout.Infinite, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ Console.WriteLine("RPC Server: Shutting down...");
+ }
+ finally
+ {
+ await responder.CloseAsync();
+ }
+ }
+ finally
+ {
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+ }
+}
+
+async Task RunClientAsync(CancellationToken cancellationToken)
+{
+ ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("rpc-amqp10-client")
+ .Build();
+
+ IEnvironment environment = AmqpEnvironment.Create(settings);
+ IConnection connection = await environment.CreateConnectionAsync();
+
+ try
+ {
+ IRequester requester = await connection.RequesterBuilder()
+ .RequestAddress()
+ .Queue(requestQueue)
+ .Requester()
+ .BuildAsync();
+
+ try
+ {
+ Console.WriteLine($"RPC Client: Reply address: {requester.GetReplyToQueue()}");
+ Console.WriteLine("RPC Client: Started. Sending ping every second (CTRL+C to exit).");
+
+ int requestId = 0;
+ using var ticker = new PeriodicTimer(TimeSpan.FromSeconds(1));
+ while (await ticker.WaitForNextTickAsync(cancellationToken))
+ {
+ requestId++;
+ IMessage reply;
+ try
+ {
+ reply = await requester.PublishAsync(new AmqpMessage(Encoding.UTF8.GetBytes("ping")),
+ cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"RPC Client: Error sending request: {ex.Message}");
+ continue;
+ }
+
+ string payload = reply.BodyAsString();
+ Console.WriteLine($"RPC Client: Sent ping request ({requestId})");
+ Console.WriteLine($"RPC Client: Received reply - {payload}");
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ Console.WriteLine("RPC Client: Shutting down...");
+ }
+ finally
+ {
+ await requester.CloseAsync();
+ }
+ }
+ finally
+ {
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+ }
+}
diff --git a/dotnet-amqp/RpcAmqp10/RpcAmqp10.csproj b/dotnet-amqp/RpcAmqp10/RpcAmqp10.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/RpcAmqp10/RpcAmqp10.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/Send/Program.cs b/dotnet-amqp/Send/Program.cs
new file mode 100644
index 00000000..0a00594e
--- /dev/null
+++ b/dotnet-amqp/Send/Program.cs
@@ -0,0 +1,56 @@
+using System.Text;
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-send")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IQueueSpecification queueSpec = management.Queue("hello").Type(QueueType.QUORUM);
+ await queueSpec.DeclareAsync();
+
+ IPublisher publisher = await connection.PublisherBuilder().Queue("hello").BuildAsync();
+ try
+ {
+ const string body = "Hello World!";
+ var message = new AmqpMessage(Encoding.UTF8.GetBytes(body));
+ PublishResult pr = await publisher.PublishAsync(message);
+ switch (pr.Outcome.State)
+ {
+ case OutcomeState.Accepted:
+ break;
+ case OutcomeState.Released:
+ Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}");
+ Environment.Exit(1);
+ break;
+ case OutcomeState.Rejected:
+ Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}");
+ Environment.Exit(1);
+ break;
+ default:
+ Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}");
+ Environment.Exit(1);
+ break;
+ }
+
+ Console.WriteLine($" [x] Sent {body}");
+ }
+ finally
+ {
+ await publisher.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
diff --git a/dotnet-amqp/Send/Send.csproj b/dotnet-amqp/Send/Send.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/Send/Send.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/Worker/Program.cs b/dotnet-amqp/Worker/Program.cs
new file mode 100644
index 00000000..cbae24cd
--- /dev/null
+++ b/dotnet-amqp/Worker/Program.cs
@@ -0,0 +1,71 @@
+using RabbitMQ.AMQP.Client;
+using RabbitMQ.AMQP.Client.Impl;
+
+const string brokerUri = "amqp://guest:guest@localhost:5672/%2f";
+const string taskQueueName = "task_queue";
+
+ConnectionSettings settings = ConnectionSettingsBuilder.Create()
+ .Uri(new Uri(brokerUri))
+ .ContainerId("tutorial-worker")
+ .Build();
+
+IEnvironment environment = AmqpEnvironment.Create(settings);
+IConnection connection = await environment.CreateConnectionAsync();
+
+try
+{
+ IManagement management = connection.Management();
+ IQueueSpecification queueSpec = management.Queue(taskQueueName).Type(QueueType.QUORUM);
+ await queueSpec.DeclareAsync();
+
+ IConsumer consumer = await connection.ConsumerBuilder()
+ .Queue(taskQueueName)
+ .InitialCredits(1)
+ .MessageHandler((ctx, message) =>
+ {
+ string body = message.BodyAsString();
+ Console.WriteLine($" [x] Received '{body}'");
+ try
+ {
+ DoWork(body);
+ }
+ finally
+ {
+ Console.WriteLine(" [x] Done");
+ ctx.Accept();
+ }
+
+ return Task.CompletedTask;
+ })
+ .BuildAndStartAsync();
+
+ try
+ {
+ Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C");
+ using var cts = new CancellationTokenSource();
+ Console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ cts.Cancel();
+ };
+ await Task.Delay(Timeout.Infinite, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ finally
+ {
+ await consumer.CloseAsync();
+ }
+}
+finally
+{
+ await connection.CloseAsync();
+ await environment.CloseAsync();
+}
+
+static void DoWork(string task)
+{
+ int dots = task.ToCharArray().Count(ch => ch == '.');
+ Thread.Sleep(dots * 1000);
+}
diff --git a/dotnet-amqp/Worker/Worker.csproj b/dotnet-amqp/Worker/Worker.csproj
new file mode 100644
index 00000000..a0a7e254
--- /dev/null
+++ b/dotnet-amqp/Worker/Worker.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0;net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-amqp/dotnet-amqp.sln b/dotnet-amqp/dotnet-amqp.sln
new file mode 100644
index 00000000..c04dc1c8
--- /dev/null
+++ b/dotnet-amqp/dotnet-amqp.sln
@@ -0,0 +1,223 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Send", "Send\Send.csproj", "{43E0129B-2E72-4F72-81E3-7C330EF2CF47}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Receive", "Receive\Receive.csproj", "{E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewTask", "NewTask\NewTask.csproj", "{5B2CCD62-8809-4323-8285-362865708A74}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker", "Worker\Worker.csproj", "{26A2EC9E-6A41-481F-B062-AEC85A271EA3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmitLog", "EmitLog\EmitLog.csproj", "{CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReceiveLogs", "ReceiveLogs\ReceiveLogs.csproj", "{8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmitLogDirect", "EmitLogDirect\EmitLogDirect.csproj", "{256AE298-12EF-41E6-AC16-B92AE1038729}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReceiveLogsDirect", "ReceiveLogsDirect\ReceiveLogsDirect.csproj", "{DB1CD74C-6816-4FB3-8E89-F481AFC272F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmitLogTopic", "EmitLogTopic\EmitLogTopic.csproj", "{1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReceiveLogsTopic", "ReceiveLogsTopic\ReceiveLogsTopic.csproj", "{B2F3320F-FB70-41F0-8EB7-F1298E6AE652}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPCServer", "RPCServer\RPCServer.csproj", "{C0C23D16-8819-4795-9CDA-B33E70A7D431}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPCClient", "RPCClient\RPCClient.csproj", "{3B406A89-7605-4FCB-BF3A-BC097378FF73}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublisherConfirms", "PublisherConfirms\PublisherConfirms.csproj", "{7DFF910C-284B-42F1-BDA2-00B4CC61719E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RpcAmqp10", "RpcAmqp10\RpcAmqp10.csproj", "{FB9E3563-8EF8-4723-A9E0-798B1C1A4250}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F2A9C10-4E5B-4D7A-9C3E-1A2B3C4D5E6F}"
+ ProjectSection(SolutionItems) = preProject
+ .gitattributes = .gitattributes
+ .gitignore = .gitignore
+ README.md = README.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x64.Build.0 = Debug|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x86.Build.0 = Debug|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|Any CPU.Build.0 = Release|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x64.ActiveCfg = Release|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x64.Build.0 = Release|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x86.ActiveCfg = Release|Any CPU
+ {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x86.Build.0 = Release|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x64.Build.0 = Debug|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x86.Build.0 = Debug|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x64.ActiveCfg = Release|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x64.Build.0 = Release|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x86.ActiveCfg = Release|Any CPU
+ {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x86.Build.0 = Release|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x64.Build.0 = Debug|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x86.Build.0 = Debug|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Release|x64.ActiveCfg = Release|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Release|x64.Build.0 = Release|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Release|x86.ActiveCfg = Release|Any CPU
+ {5B2CCD62-8809-4323-8285-362865708A74}.Release|x86.Build.0 = Release|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x64.Build.0 = Debug|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x86.Build.0 = Debug|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x64.ActiveCfg = Release|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x64.Build.0 = Release|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x86.ActiveCfg = Release|Any CPU
+ {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x86.Build.0 = Release|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x64.Build.0 = Debug|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x86.Build.0 = Debug|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x64.ActiveCfg = Release|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x64.Build.0 = Release|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x86.ActiveCfg = Release|Any CPU
+ {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x86.Build.0 = Release|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x64.Build.0 = Debug|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x86.Build.0 = Debug|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x64.ActiveCfg = Release|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x64.Build.0 = Release|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x86.ActiveCfg = Release|Any CPU
+ {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x86.Build.0 = Release|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x64.Build.0 = Debug|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x86.Build.0 = Debug|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|Any CPU.Build.0 = Release|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x64.ActiveCfg = Release|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x64.Build.0 = Release|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x86.ActiveCfg = Release|Any CPU
+ {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x86.Build.0 = Release|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x64.Build.0 = Debug|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x86.Build.0 = Debug|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x64.ActiveCfg = Release|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x64.Build.0 = Release|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x86.ActiveCfg = Release|Any CPU
+ {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x86.Build.0 = Release|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x64.Build.0 = Debug|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x86.Build.0 = Debug|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x64.ActiveCfg = Release|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x64.Build.0 = Release|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x86.ActiveCfg = Release|Any CPU
+ {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x86.Build.0 = Release|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x64.Build.0 = Debug|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x86.Build.0 = Debug|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x64.ActiveCfg = Release|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x64.Build.0 = Release|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x86.ActiveCfg = Release|Any CPU
+ {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x86.Build.0 = Release|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x64.Build.0 = Debug|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x86.Build.0 = Debug|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x64.ActiveCfg = Release|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x64.Build.0 = Release|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x86.ActiveCfg = Release|Any CPU
+ {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x86.Build.0 = Release|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x64.Build.0 = Debug|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x86.Build.0 = Debug|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x64.ActiveCfg = Release|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x64.Build.0 = Release|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x86.ActiveCfg = Release|Any CPU
+ {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x86.Build.0 = Release|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x64.Build.0 = Debug|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x86.Build.0 = Debug|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x64.ActiveCfg = Release|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x64.Build.0 = Release|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x86.ActiveCfg = Release|Any CPU
+ {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x86.Build.0 = Release|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x64.Build.0 = Debug|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x86.Build.0 = Debug|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x64.ActiveCfg = Release|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x64.Build.0 = Release|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x86.ActiveCfg = Release|Any CPU
+ {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/dotnet-amqp/test-tutorials.sh b/dotnet-amqp/test-tutorials.sh
new file mode 100755
index 00000000..8dde91ba
--- /dev/null
+++ b/dotnet-amqp/test-tutorials.sh
@@ -0,0 +1,280 @@
+#!/usr/bin/env bash
+# End-to-end smoke tests for dotnet-amqp tutorials.
+# Requires: bash, dotnet 8+, RabbitMQ 4.x on localhost:5672 (guest/guest).
+# Does not start RabbitMQ; ensure the broker is already running.
+#
+# Usage:
+# ./dotnet-amqp/test-tutorials.sh
+# cd dotnet-amqp && ./test-tutorials.sh
+#
+# Environment:
+# SKIP_BROKER_CHECK=1 Skip TCP check on port 5672 (tests still need a broker)
+# SKIP_MGMT_DELETE=1 Do not DELETE tutorial queues via management HTTP API before T1/T2
+#
+# If tutorial queues (hello, task_queue) already have consumers from other processes, T1/T2 can
+# fail until those queues are deleted or the other consumers stop. By default this script tries
+# DELETE http://localhost:15672/api/queues/%2F/ (requires the management plugin).
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$ROOT"
+
+PASS=0
+FAIL=0
+
+pass() {
+ printf 'ok\t%s\n' "$1"
+ PASS=$((PASS + 1))
+}
+
+fail() {
+ printf 'FAIL\t%s\n' "$1" >&2
+ FAIL=$((FAIL + 1))
+}
+
+require_dotnet() {
+ command -v dotnet >/dev/null 2>&1 || {
+ echo "dotnet is not on PATH" >&2
+ exit 1
+ }
+}
+
+check_broker() {
+ if command -v nc >/dev/null 2>&1; then
+ nc -z localhost 5672 2>/dev/null
+ return
+ fi
+ { echo >/dev/tcp/127.0.0.1/5672; } 2>/dev/null
+}
+
+require_broker() {
+ if [[ "${SKIP_BROKER_CHECK:-}" == "1" ]]; then
+ return 0
+ fi
+ if ! check_broker; then
+ echo "RabbitMQ does not appear to be reachable on localhost:5672" >&2
+ echo "Start the broker or set SKIP_BROKER_CHECK=1 to skip this check." >&2
+ exit 1
+ fi
+}
+
+build_once() {
+ dotnet build "$ROOT/dotnet-amqp.sln" -c Release -v minimal >/dev/null
+}
+
+# Wait until log file contains a line (consumer ready, etc.). Handles slow `dotnet run` cold start.
+wait_for_log() {
+ local file=$1
+ local needle=$2
+ local max_attempts=${3:-120}
+ local i=0
+ while [[ $i -lt $max_attempts ]]; do
+ if [[ -f "$file" ]] && grep -qF -- "$needle" "$file" 2>/dev/null; then
+ return 0
+ fi
+ sleep 0.25
+ i=$((i + 1))
+ done
+ return 1
+}
+
+# Remove a queue so stale consumers from other tutorial runs cannot steal messages (needs management plugin).
+delete_queue_mgmt() {
+ local name=$1
+ if [[ "${SKIP_MGMT_DELETE:-}" == "1" ]]; then
+ return 0
+ fi
+ if ! command -v curl >/dev/null 2>&1; then
+ return 0
+ fi
+ curl -sf -o /dev/null -u guest:guest -X DELETE "http://localhost:15672/api/queues/%2F/${name}" 2>/dev/null || true
+}
+
+# dotnet run --project Dir/Dir.csproj -- args...
+# Use line-buffered stdout when not attached to a TTY so logs capture "Received..." lines before kill.
+runproj() {
+ local proj=$1
+ shift
+ if command -v stdbuf >/dev/null 2>&1; then
+ stdbuf -oL -eL dotnet run --no-build -c Release -f net8.0 --project "$ROOT/$proj/$proj.csproj" -- "$@"
+ else
+ dotnet run --no-build -c Release -f net8.0 --project "$ROOT/$proj/$proj.csproj" -- "$@"
+ fi
+}
+
+assert_file_contains() {
+ local file=$1
+ local needle=$2
+ local msg=$3
+ if [[ -f "$file" ]] && grep -qF -- "$needle" "$file"; then
+ pass "$msg"
+ else
+ fail "$msg (log should contain: $needle)"
+ if [[ -f "$file" ]]; then
+ echo "--- $file (first 40 lines) ---" >&2
+ head -n 40 "$file" >&2
+ fi
+ fi
+}
+
+# Run command in background; sleep $secs; kill. Captures stdout/stderr to $log.
+run_timed() {
+ local log=$1
+ local secs=$2
+ shift 2
+ : >"$log"
+ "$@" >"$log" 2>&1 &
+ local pid=$!
+ sleep "$secs"
+ kill "$pid" 2>/dev/null || true
+ wait "$pid" 2>/dev/null || true
+}
+
+test_t1_hello() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ delete_queue_mgmt hello
+ runproj Receive >"$recv_log" 2>&1 &
+ local rpid=$!
+ wait_for_log "$recv_log" "Waiting for messages" || true
+ sleep 1
+ runproj Send >"$log" 2>&1
+ sleep 3
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T1 send publishes"
+ assert_file_contains "$recv_log" "Hello World" "T1 receive gets message"
+ rm -f "$log" "$recv_log"
+}
+
+test_t2_work_queues() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ delete_queue_mgmt task_queue
+ runproj Worker >"$recv_log" 2>&1 &
+ local rpid=$!
+ wait_for_log "$recv_log" "Waiting for messages" || true
+ sleep 1
+ runproj NewTask "e2e.task.test" >"$log" 2>&1
+ sleep 4
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T2 NewTask publishes"
+ assert_file_contains "$recv_log" "e2e.task.test" "T2 Worker receives task"
+ rm -f "$log" "$recv_log"
+}
+
+test_t3_pubsub() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ runproj ReceiveLogs >"$recv_log" 2>&1 &
+ local rpid=$!
+ wait_for_log "$recv_log" "Waiting for messages" || true
+ sleep 1
+ runproj EmitLog "e2e" "pubsub" "hello" >"$log" 2>&1
+ sleep 3
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T3 EmitLog publishes"
+ assert_file_contains "$recv_log" "e2e pubsub hello" "T3 ReceiveLogs receives broadcast"
+ rm -f "$log" "$recv_log"
+}
+
+test_t4_routing() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ runproj ReceiveLogsDirect warn >"$recv_log" 2>&1 &
+ local rpid=$!
+ wait_for_log "$recv_log" "Waiting for messages" || true
+ sleep 1
+ runproj EmitLogDirect warn "e2e routing" >"$log" 2>&1
+ sleep 3
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T4 EmitLogDirect publishes"
+ assert_file_contains "$recv_log" "e2e routing" "T4 ReceiveLogsDirect gets routed message"
+ rm -f "$log" "$recv_log"
+}
+
+test_t5_topics() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ runproj ReceiveLogsTopic "kern.*" >"$recv_log" 2>&1 &
+ local rpid=$!
+ wait_for_log "$recv_log" "Waiting for messages" || true
+ sleep 1
+ runproj EmitLogTopic kern.info "e2e topic" >"$log" 2>&1
+ sleep 3
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T5 EmitLogTopic publishes"
+ assert_file_contains "$recv_log" "e2e topic" "T5 ReceiveLogsTopic matches binding"
+ rm -f "$log" "$recv_log"
+}
+
+test_t6_rpc() {
+ local log srv_log
+ log=$(mktemp)
+ srv_log=$(mktemp)
+ runproj RPCServer >"$srv_log" 2>&1 &
+ local spid=$!
+ sleep 3
+ if ! runproj RPCClient >"$log" 2>&1; then
+ kill "$spid" 2>/dev/null || true
+ wait "$spid" 2>/dev/null || true
+ fail "T6 RPCClient process failed"
+ rm -f "$log" "$srv_log"
+ return
+ fi
+ kill "$spid" 2>/dev/null || true
+ wait "$spid" 2>/dev/null || true
+ assert_file_contains "$log" "Requesting fib(10)" "T6 RPCClient sends request"
+ assert_file_contains "$log" "Got '55'" "T6 RPCClient gets fib(10)==55"
+ rm -f "$log" "$srv_log"
+}
+
+test_publisher_confirms() {
+ local log
+ log=$(mktemp)
+ run_timed "$log" 6 runproj PublisherConfirms
+ assert_file_contains "$log" "Accepted Message" "PublisherConfirms sees accepted publish"
+ assert_file_contains "$log" "Received a message" "PublisherConfirms consumer sees message"
+ rm -f "$log"
+}
+
+test_rpc_amqp10() {
+ local log
+ log=$(mktemp)
+ run_timed "$log" 8 runproj RpcAmqp10
+ assert_file_contains "$log" "pong" "RpcAmqp10 ping/pong (Direct Reply-To)"
+ rm -f "$log"
+}
+
+main() {
+ require_dotnet
+ require_broker
+ echo "Building dotnet-amqp (Release)..."
+ build_once
+ echo "Running dotnet-amqp tutorial smoke tests from $ROOT"
+ test_t1_hello
+ test_t2_work_queues
+ test_t3_pubsub
+ test_t4_routing
+ test_t5_topics
+ test_t6_rpc
+ test_publisher_confirms
+ test_rpc_amqp10
+ echo "----"
+ echo "Passed: $PASS Failed: $FAIL"
+ if [[ "$FAIL" -gt 0 ]]; then
+ exit 1
+ fi
+}
+
+main "$@"
diff --git a/go-amqp/.gitignore b/go-amqp/.gitignore
new file mode 100644
index 00000000..8af6f160
--- /dev/null
+++ b/go-amqp/.gitignore
@@ -0,0 +1,2 @@
+pkg/*
+src/*
diff --git a/go-amqp/README.md b/go-amqp/README.md
new file mode 100644
index 00000000..76b0f9d1
--- /dev/null
+++ b/go-amqp/README.md
@@ -0,0 +1,58 @@
+# Go code for RabbitMQ tutorials (AMQP 1.0)
+
+
+Here you can find Go code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html), using the AMQP 1.0 client.
+
+
+## Requirements
+
+These examples use the [`rabbitmq-amqp-go-client`](https://github.com/rabbitmq/rabbitmq-amqp-go-client) library for RabbitMQ 4.x. Get it with
+
+ go get github.com/rabbitmq/rabbitmq-amqp-go-client
+
+A RabbitMQ node must be running on `localhost` with the default port (`5672`) and credentials (`guest` / `guest`).
+
+
+## Code
+
+Run each example from this directory with `go run`:
+
+[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-go.html):
+
+ go run send.go
+ go run receive.go
+
+[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-go.html):
+
+ go run new_task.go hello world
+ go run worker.go
+
+[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-go.html)
+
+ go run receive_logs.go
+ go run emit_log.go hello world
+
+[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-go.html)
+
+ go run receive_logs_direct.go info warn
+ go run emit_log_direct.go warn "a warning"
+
+[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-go.html)
+
+ go run receive_logs_topic.go "kern.*" "*.critical"
+ go run emit_log_topic.go kern.critical "A critical kernel error"
+
+[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-go.html)
+
+ go run rpc_server.go
+ go run rpc_client.go 10
+
+[Publisher confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-java.html) (AMQP 1.0 publish outcomes)
+
+ go run publisher_confirms.go
+
+[AMQP 1.0 Direct Reply-To RPC](https://www.rabbitmq.com/docs/next/direct-reply-to)
+
+ go run rpc_amqp10.go
+
+To learn more, see the [package documentation](https://pkg.go.dev/github.com/rabbitmq/rabbitmq-amqp-go-client) and [AMQP in RabbitMQ](https://www.rabbitmq.com/docs/amqp).
diff --git a/go-amqp/emit_log.go b/go-amqp/emit_log.go
new file mode 100644
index 00000000..4beb174b
--- /dev/null
+++ b/go-amqp/emit_log.go
@@ -0,0 +1,63 @@
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "strings"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareExchange(ctx, &rmq.FanOutExchangeSpecification{Name: "logs"})
+ if err != nil {
+ log.Panicf("Failed to declare an exchange: %v", err)
+ }
+
+ publisher, err := conn.NewPublisher(ctx, &rmq.ExchangeAddress{Exchange: "logs", Key: ""}, nil)
+ if err != nil {
+ log.Panicf("Failed to create publisher: %v", err)
+ }
+ defer func() { _ = publisher.Close(context.Background()) }()
+
+ body := bodyFrom(os.Args)
+ res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body)))
+ if err != nil {
+ log.Panicf("Failed to publish a message: %v", err)
+ }
+ switch res.Outcome.(type) {
+ case *rmq.StateAccepted:
+ case *rmq.StateRejected:
+ log.Fatalf("Message was rejected: %v", res.Outcome)
+ case *rmq.StateReleased:
+ log.Fatalf("Message was released: %v", res.Outcome)
+ case *rmq.StateModified:
+ log.Fatalf("Message was modified: %v", res.Outcome)
+ default:
+ log.Fatalf("Unexpected publish outcome: %v", res.Outcome)
+ }
+ log.Printf(" [x] Sent %s", body)
+}
+
+func bodyFrom(args []string) string {
+ var s string
+ if (len(args) < 2) || args[1] == "" {
+ s = "hello"
+ } else {
+ s = strings.Join(args[1:], " ")
+ }
+ return s
+}
diff --git a/go-amqp/emit_log_direct.go b/go-amqp/emit_log_direct.go
new file mode 100644
index 00000000..28e54dc9
--- /dev/null
+++ b/go-amqp/emit_log_direct.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "strings"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareExchange(ctx, &rmq.DirectExchangeSpecification{Name: "logs_direct"})
+ if err != nil {
+ log.Panicf("Failed to declare an exchange: %v", err)
+ }
+
+ publisher, err := conn.NewPublisher(ctx, &rmq.ExchangeAddress{
+ Exchange: "logs_direct",
+ Key: severityFrom(os.Args),
+ }, nil)
+ if err != nil {
+ log.Panicf("Failed to create publisher: %v", err)
+ }
+ defer func() { _ = publisher.Close(context.Background()) }()
+
+ body := bodyFrom(os.Args)
+ res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body)))
+ if err != nil {
+ log.Panicf("Failed to publish a message: %v", err)
+ }
+ switch res.Outcome.(type) {
+ case *rmq.StateAccepted:
+ case *rmq.StateRejected:
+ log.Fatalf("Message was rejected: %v", res.Outcome)
+ case *rmq.StateReleased:
+ log.Fatalf("Message was released: %v", res.Outcome)
+ case *rmq.StateModified:
+ log.Fatalf("Message was modified: %v", res.Outcome)
+ default:
+ log.Fatalf("Unexpected publish outcome: %v", res.Outcome)
+ }
+ log.Printf(" [x] Sent %s", body)
+}
+
+func bodyFrom(args []string) string {
+ var s string
+ if (len(args) < 3) || args[2] == "" {
+ s = "hello"
+ } else {
+ s = strings.Join(args[2:], " ")
+ }
+ return s
+}
+
+func severityFrom(args []string) string {
+ var s string
+ if (len(args) < 2) || args[1] == "" {
+ s = "info"
+ } else {
+ s = args[1]
+ }
+ return s
+}
diff --git a/go-amqp/emit_log_topic.go b/go-amqp/emit_log_topic.go
new file mode 100644
index 00000000..824c463c
--- /dev/null
+++ b/go-amqp/emit_log_topic.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "strings"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareExchange(ctx, &rmq.TopicExchangeSpecification{Name: "logs_topic"})
+ if err != nil {
+ log.Panicf("Failed to declare an exchange: %v", err)
+ }
+
+ publisher, err := conn.NewPublisher(ctx, &rmq.ExchangeAddress{
+ Exchange: "logs_topic",
+ Key: severityFrom(os.Args),
+ }, nil)
+ if err != nil {
+ log.Panicf("Failed to create publisher: %v", err)
+ }
+ defer func() { _ = publisher.Close(context.Background()) }()
+
+ body := bodyFrom(os.Args)
+ res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body)))
+ if err != nil {
+ log.Panicf("Failed to publish a message: %v", err)
+ }
+ switch res.Outcome.(type) {
+ case *rmq.StateAccepted:
+ case *rmq.StateRejected:
+ log.Fatalf("Message was rejected: %v", res.Outcome)
+ case *rmq.StateReleased:
+ log.Fatalf("Message was released: %v", res.Outcome)
+ case *rmq.StateModified:
+ log.Fatalf("Message was modified: %v", res.Outcome)
+ default:
+ log.Fatalf("Unexpected publish outcome: %v", res.Outcome)
+ }
+ log.Printf(" [x] Sent %s", body)
+}
+
+func bodyFrom(args []string) string {
+ var s string
+ if (len(args) < 3) || args[2] == "" {
+ s = "hello"
+ } else {
+ s = strings.Join(args[2:], " ")
+ }
+ return s
+}
+
+func severityFrom(args []string) string {
+ var s string
+ if (len(args) < 2) || args[1] == "" {
+ s = "anonymous.info"
+ } else {
+ s = args[1]
+ }
+ return s
+}
diff --git a/go-amqp/go.mod b/go-amqp/go.mod
new file mode 100644
index 00000000..27d18182
--- /dev/null
+++ b/go-amqp/go.mod
@@ -0,0 +1,16 @@
+module github.com/rabbitmq/rabbitmq-tutorials/go-amqp
+
+go 1.24.0
+
+require (
+ github.com/Azure/go-amqp v1.5.1
+ github.com/rabbitmq/rabbitmq-amqp-go-client v1.0.0
+)
+
+require (
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/gorilla/websocket v1.5.3 // indirect
+ go.opentelemetry.io/otel v1.40.0 // indirect
+ go.opentelemetry.io/otel/metric v1.40.0 // indirect
+)
diff --git a/go-amqp/go.sum b/go-amqp/go.sum
new file mode 100644
index 00000000..ce0187f1
--- /dev/null
+++ b/go-amqp/go.sum
@@ -0,0 +1,56 @@
+github.com/Azure/go-amqp v1.5.1 h1:WyiPTz2C3zVvDL7RLAqwWdeoYhMtX62MZzQoP09fzsU=
+github.com/Azure/go-amqp v1.5.1/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
+github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
+github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
+github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
+github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
+github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rabbitmq/rabbitmq-amqp-go-client v0.7.0 h1:mQnrLwgZbYwf6CdRJ2l2kTf8cbn9j4AyBJM9tXnwebg=
+github.com/rabbitmq/rabbitmq-amqp-go-client v0.7.0/go.mod h1:u2HAJ0fUFyayNcIvTGCTlpmdPKHrKO9Xl81FbiEzYO4=
+github.com/rabbitmq/rabbitmq-amqp-go-client v1.0.0 h1:VvrVB+Fz7g0hg0ZcxDXf+WHShQsfQ1iI5FbT5ZnJ3lY=
+github.com/rabbitmq/rabbitmq-amqp-go-client v1.0.0/go.mod h1:u2HAJ0fUFyayNcIvTGCTlpmdPKHrKO9Xl81FbiEzYO4=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
+go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
+go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
+go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
+go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
+go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
+go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
+go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
+golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
+golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
+golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
+golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
+golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/go-amqp/new_task.go b/go-amqp/new_task.go
new file mode 100644
index 00000000..dbf07792
--- /dev/null
+++ b/go-amqp/new_task.go
@@ -0,0 +1,63 @@
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "strings"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "task_queue"})
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ publisher, err := conn.NewPublisher(ctx, &rmq.QueueAddress{Queue: "task_queue"}, nil)
+ if err != nil {
+ log.Panicf("Failed to create publisher: %v", err)
+ }
+ defer func() { _ = publisher.Close(context.Background()) }()
+
+ body := bodyFrom(os.Args)
+ res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body)))
+ if err != nil {
+ log.Panicf("Failed to publish a message: %v", err)
+ }
+ switch res.Outcome.(type) {
+ case *rmq.StateAccepted:
+ case *rmq.StateRejected:
+ log.Fatalf("Message was rejected: %v", res.Outcome)
+ case *rmq.StateReleased:
+ log.Fatalf("Message was released: %v", res.Outcome)
+ case *rmq.StateModified:
+ log.Fatalf("Message was modified: %v", res.Outcome)
+ default:
+ log.Fatalf("Unexpected publish outcome: %v", res.Outcome)
+ }
+ log.Printf(" [x] Sent %s", body)
+}
+
+func bodyFrom(args []string) string {
+ var s string
+ if (len(args) < 2) || args[1] == "" {
+ s = "hello"
+ } else {
+ s = strings.Join(args[1:], " ")
+ }
+ return s
+}
diff --git a/go-amqp/publisher_confirms.go b/go-amqp/publisher_confirms.go
new file mode 100644
index 00000000..765b0530
--- /dev/null
+++ b/go-amqp/publisher_confirms.go
@@ -0,0 +1,87 @@
+package main
+
+import (
+ "context"
+ "log"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{
+ IsExclusive: false,
+ IsAutoDelete: false,
+ })
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ consume(conn, qInfo.Name())
+ publish(conn, qInfo.Name(), "hello")
+
+ log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
+ select {}
+}
+
+func consume(conn *rmq.AmqpConnection, qName string) {
+ ctx := context.Background()
+ consumer, err := conn.NewConsumer(ctx, qName, nil)
+ if err != nil {
+ log.Panicf("Failed to create consumer: %v", err)
+ }
+
+ go func() {
+ for {
+ delivery, err := consumer.Receive(ctx)
+ if err != nil {
+ log.Printf("consumer receive: %v", err)
+ return
+ }
+ msg := delivery.Message()
+ var body string
+ if len(msg.Data) > 0 {
+ body = string(msg.Data[0])
+ }
+ log.Printf("Received a message: %s", body)
+ _ = delivery.Accept(ctx)
+ }
+ }()
+}
+
+func publish(conn *rmq.AmqpConnection, qName, text string) {
+ ctx := context.Background()
+ publisher, err := conn.NewPublisher(ctx, &rmq.QueueAddress{Queue: qName}, nil)
+ if err != nil {
+ log.Panicf("Failed to create publisher: %v", err)
+ }
+ defer func() { _ = publisher.Close(context.Background()) }()
+
+ res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(text)))
+ if err != nil {
+ log.Panicf("Failed to publish a message: %v", err)
+ }
+ switch res.Outcome.(type) {
+ case *rmq.StateAccepted:
+ log.Printf("Confirmed")
+ case *rmq.StateRejected:
+ log.Fatalf("Message was rejected: %v", res.Outcome)
+ case *rmq.StateReleased:
+ log.Fatalf("Message was released: %v", res.Outcome)
+ case *rmq.StateModified:
+ log.Fatalf("Message was modified: %v", res.Outcome)
+ default:
+ log.Fatalf("Unexpected publish outcome: %v", res.Outcome)
+ }
+}
diff --git a/go-amqp/receive.go b/go-amqp/receive.go
new file mode 100644
index 00000000..40b32344
--- /dev/null
+++ b/go-amqp/receive.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "log"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "hello"})
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ consumer, err := conn.NewConsumer(ctx, "hello", nil)
+ if err != nil {
+ log.Panicf("Failed to create consumer: %v", err)
+ }
+ defer func() { _ = consumer.Close(context.Background()) }()
+
+ log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
+ for {
+ delivery, err := consumer.Receive(ctx)
+ if err != nil {
+ if errors.Is(err, context.Canceled) {
+ return
+ }
+ log.Panicf("Failed to receive a message: %v", err)
+ }
+ msg := delivery.Message()
+ var body string
+ if len(msg.Data) > 0 {
+ body = string(msg.Data[0])
+ }
+ log.Printf("Received a message: %s", body)
+ err = delivery.Accept(ctx)
+ if err != nil {
+ log.Panicf("Failed to accept message: %v", err)
+ }
+ }
+}
diff --git a/go-amqp/receive_logs.go b/go-amqp/receive_logs.go
new file mode 100644
index 00000000..1842c341
--- /dev/null
+++ b/go-amqp/receive_logs.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "log"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareExchange(ctx, &rmq.FanOutExchangeSpecification{Name: "logs"})
+ if err != nil {
+ log.Panicf("Failed to declare an exchange: %v", err)
+ }
+
+ qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{
+ IsExclusive: true,
+ IsAutoDelete: true,
+ })
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ _, err = conn.Management().Bind(ctx, &rmq.ExchangeToQueueBindingSpecification{
+ SourceExchange: "logs",
+ DestinationQueue: qInfo.Name(),
+ BindingKey: "",
+ })
+ if err != nil {
+ log.Panicf("Failed to bind a queue: %v", err)
+ }
+
+ consumer, err := conn.NewConsumer(ctx, qInfo.Name(), nil)
+ if err != nil {
+ log.Panicf("Failed to create consumer: %v", err)
+ }
+ defer func() { _ = consumer.Close(context.Background()) }()
+
+ log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
+ for {
+ delivery, err := consumer.Receive(ctx)
+ if err != nil {
+ if errors.Is(err, context.Canceled) {
+ return
+ }
+ log.Panicf("Failed to receive a message: %v", err)
+ }
+ msg := delivery.Message()
+ var body string
+ if len(msg.Data) > 0 {
+ body = string(msg.Data[0])
+ }
+ log.Printf(" [x] %s", body)
+ err = delivery.Accept(ctx)
+ if err != nil {
+ log.Panicf("Failed to accept message: %v", err)
+ }
+ }
+}
diff --git a/go-amqp/receive_logs_direct.go b/go-amqp/receive_logs_direct.go
new file mode 100644
index 00000000..eaaf6b72
--- /dev/null
+++ b/go-amqp/receive_logs_direct.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "log"
+ "os"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareExchange(ctx, &rmq.DirectExchangeSpecification{Name: "logs_direct"})
+ if err != nil {
+ log.Panicf("Failed to declare an exchange: %v", err)
+ }
+
+ qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{
+ IsExclusive: true,
+ IsAutoDelete: true,
+ })
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ if len(os.Args) < 2 {
+ log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
+ os.Exit(0)
+ }
+ for _, s := range os.Args[1:] {
+ log.Printf("Binding queue %s to exchange %s with routing key %s", qInfo.Name(), "logs_direct", s)
+ _, err = conn.Management().Bind(ctx, &rmq.ExchangeToQueueBindingSpecification{
+ SourceExchange: "logs_direct",
+ DestinationQueue: qInfo.Name(),
+ BindingKey: s,
+ })
+ if err != nil {
+ log.Panicf("Failed to bind a queue: %v", err)
+ }
+ }
+
+ consumer, err := conn.NewConsumer(ctx, qInfo.Name(), nil)
+ if err != nil {
+ log.Panicf("Failed to create consumer: %v", err)
+ }
+ defer func() { _ = consumer.Close(context.Background()) }()
+
+ log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
+ for {
+ delivery, err := consumer.Receive(ctx)
+ if err != nil {
+ if errors.Is(err, context.Canceled) {
+ return
+ }
+ log.Panicf("Failed to receive a message: %v", err)
+ }
+ msg := delivery.Message()
+ var body string
+ if len(msg.Data) > 0 {
+ body = string(msg.Data[0])
+ }
+ log.Printf(" [x] %s", body)
+ err = delivery.Accept(ctx)
+ if err != nil {
+ log.Panicf("Failed to accept message: %v", err)
+ }
+ }
+}
diff --git a/go-amqp/receive_logs_topic.go b/go-amqp/receive_logs_topic.go
new file mode 100644
index 00000000..ea33b5ec
--- /dev/null
+++ b/go-amqp/receive_logs_topic.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "log"
+ "os"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareExchange(ctx, &rmq.TopicExchangeSpecification{Name: "logs_topic"})
+ if err != nil {
+ log.Panicf("Failed to declare an exchange: %v", err)
+ }
+
+ qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{
+ IsExclusive: true,
+ IsAutoDelete: true,
+ })
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ if len(os.Args) < 2 {
+ log.Printf("Usage: %s [binding_key]...", os.Args[0])
+ os.Exit(0)
+ }
+ for _, s := range os.Args[1:] {
+ log.Printf("Binding queue %s to exchange %s with routing key %s", qInfo.Name(), "logs_topic", s)
+ _, err = conn.Management().Bind(ctx, &rmq.ExchangeToQueueBindingSpecification{
+ SourceExchange: "logs_topic",
+ DestinationQueue: qInfo.Name(),
+ BindingKey: s,
+ })
+ if err != nil {
+ log.Panicf("Failed to bind a queue: %v", err)
+ }
+ }
+
+ consumer, err := conn.NewConsumer(ctx, qInfo.Name(), nil)
+ if err != nil {
+ log.Panicf("Failed to create consumer: %v", err)
+ }
+ defer func() { _ = consumer.Close(context.Background()) }()
+
+ log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
+ for {
+ delivery, err := consumer.Receive(ctx)
+ if err != nil {
+ if errors.Is(err, context.Canceled) {
+ return
+ }
+ log.Panicf("Failed to receive a message: %v", err)
+ }
+ msg := delivery.Message()
+ var body string
+ if len(msg.Data) > 0 {
+ body = string(msg.Data[0])
+ }
+ log.Printf(" [x] %s", body)
+ err = delivery.Accept(ctx)
+ if err != nil {
+ log.Panicf("Failed to accept message: %v", err)
+ }
+ }
+}
diff --git a/go-amqp/rpc_amqp10.go b/go-amqp/rpc_amqp10.go
new file mode 100644
index 00000000..e586edc6
--- /dev/null
+++ b/go-amqp/rpc_amqp10.go
@@ -0,0 +1,151 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ amqp "github.com/Azure/go-amqp"
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const (
+ amqpURL = "amqp://guest:guest@localhost:5672/"
+ requestQueue = "rpc-requests"
+)
+
+func rpcServer(ctx context.Context) error {
+ env := rmq.NewEnvironment(amqpURL, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to connect to RabbitMQ: %w", err)
+ }
+ defer func() { _ = env.CloseConnections(context.Background()) }()
+
+ _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: requestQueue})
+ if err != nil {
+ return fmt.Errorf("failed to declare queue: %w", err)
+ }
+
+ responder, err := conn.NewResponder(ctx, rmq.ResponderOptions{
+ RequestQueue: requestQueue,
+ Handler: func(_ context.Context, request *amqp.Message) (*amqp.Message, error) {
+ var payload string
+ if len(request.Data) > 0 {
+ payload = string(request.Data[0])
+ }
+ if request.Properties != nil && request.Properties.MessageID != nil {
+ log.Printf("RPC Server: Received %s request (ID: %v)", payload, request.Properties.MessageID)
+ } else {
+ log.Printf("RPC Server: Received %s request", payload)
+ }
+ return amqp.NewMessage([]byte("pong")), nil
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create responder: %w", err)
+ }
+ defer func() { _ = responder.Close(context.Background()) }()
+
+ log.Println("RPC Server: Started and listening for requests...")
+
+ <-ctx.Done()
+ log.Println("RPC Server: Shutting down...")
+ return nil
+}
+
+func rpcClient(ctx context.Context) error {
+ env := rmq.NewEnvironment(amqpURL, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to connect to RabbitMQ: %w", err)
+ }
+ defer func() { _ = env.CloseConnections(context.Background()) }()
+
+ requester, err := conn.NewRequester(ctx, &rmq.RequesterOptions{
+ RequestQueueName: requestQueue,
+ SettleStrategy: rmq.DirectReplyTo,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create requester: %w", err)
+ }
+ defer func() { _ = requester.Close(context.Background()) }()
+
+ replyAddr, err := requester.GetReplyQueue()
+ if err != nil {
+ return err
+ }
+ log.Printf("RPC Client: Reply address: %s", replyAddr)
+
+ log.Println("RPC Client: Started and ready to send requests...")
+
+ requestID := 0
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ctx.Done():
+ log.Println("RPC Client: Shutting down...")
+ return nil
+ case <-ticker.C:
+ requestID++
+ replyCh, err := requester.Publish(ctx, requester.Message([]byte("ping")))
+ if err != nil {
+ log.Printf("RPC Client: Error sending request: %v", err)
+ continue
+ }
+ log.Printf("RPC Client: Sent ping request (%d)", requestID)
+
+ reply := <-replyCh
+ if reply == nil {
+ log.Printf("RPC Client: No reply for request %d", requestID)
+ continue
+ }
+ var payload string
+ if len(reply.Data) > 0 {
+ payload = string(reply.Data[0])
+ }
+ log.Printf("RPC Client: Received reply - %s", payload)
+ }
+ }
+}
+
+func main() {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ log.Println("Starting Direct Reply-To RPC example...")
+ log.Println("Make sure RabbitMQ is running on localhost:5672")
+
+ go func() {
+ if err := rpcServer(ctx); err != nil {
+ log.Printf("RPC Server error: %v", err)
+ cancel()
+ }
+ }()
+
+ time.Sleep(500 * time.Millisecond)
+
+ go func() {
+ if err := rpcClient(ctx); err != nil {
+ log.Printf("RPC Client error: %v", err)
+ cancel()
+ }
+ }()
+
+ sigs := make(chan os.Signal, 1)
+ signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
+ go func() {
+ <-sigs
+ log.Println("\nReceived interrupt, shutting down...")
+ cancel()
+ }()
+
+ <-ctx.Done()
+ log.Println("Application shutting down...")
+}
diff --git a/go-amqp/rpc_client.go b/go-amqp/rpc_client.go
new file mode 100644
index 00000000..c901a40f
--- /dev/null
+++ b/go-amqp/rpc_client.go
@@ -0,0 +1,74 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "log"
+ "os"
+ "strconv"
+ "strings"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func fibonacciRPC(n int) (res int, err error) {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ return 0, err
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ requester, err := conn.NewRequester(ctx, &rmq.RequesterOptions{
+ RequestQueueName: "rpc_queue",
+ })
+ if err != nil {
+ return 0, err
+ }
+ defer func() { _ = requester.Close(context.Background()) }()
+
+ replyCh, err := requester.Publish(ctx, requester.Message([]byte(strconv.Itoa(n))))
+ if err != nil {
+ return 0, err
+ }
+ reply := <-replyCh
+ if reply == nil {
+ return 0, errors.New("no reply received")
+ }
+ var payload []byte
+ if len(reply.Data) > 0 {
+ payload = reply.Data[0]
+ }
+ return strconv.Atoi(string(payload))
+}
+
+func main() {
+ n := bodyFrom(os.Args)
+
+ log.Printf(" [x] Requesting fib(%d)", n)
+ res, err := fibonacciRPC(n)
+ if err != nil {
+ log.Panicf("Failed to handle RPC request: %v", err)
+ }
+
+ log.Printf(" [.] Got %d", res)
+}
+
+func bodyFrom(args []string) int {
+ var s string
+ if (len(args) < 2) || args[1] == "" {
+ s = "30"
+ } else {
+ s = strings.Join(args[1:], " ")
+ }
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ log.Panicf("Failed to convert arg to integer: %v", err)
+ }
+ return n
+}
diff --git a/go-amqp/rpc_server.go b/go-amqp/rpc_server.go
new file mode 100644
index 00000000..199cbd75
--- /dev/null
+++ b/go-amqp/rpc_server.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "context"
+ "log"
+ "strconv"
+
+ amqp "github.com/Azure/go-amqp"
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func fib(n int) int {
+ if n == 0 {
+ return 0
+ } else if n == 1 {
+ return 1
+ }
+ return fib(n-1) + fib(n-2)
+}
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "rpc_queue"})
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+ _, err = conn.Management().PurgeQueue(ctx, "rpc_queue")
+ if err != nil {
+ log.Printf("purge: %v", err)
+ }
+
+ responder, err := conn.NewResponder(ctx, rmq.ResponderOptions{
+ RequestQueue: "rpc_queue",
+ Handler: func(_ context.Context, request *amqp.Message) (*amqp.Message, error) {
+ var payload []byte
+ if len(request.Data) > 0 {
+ payload = request.Data[0]
+ }
+ n, err := strconv.Atoi(string(payload))
+ if err != nil {
+ return nil, err
+ }
+ log.Printf(" [.] fib(%d)", n)
+ response := fib(n)
+ return amqp.NewMessage([]byte(strconv.Itoa(response))), nil
+ },
+ })
+ if err != nil {
+ log.Panicf("Failed to create responder: %v", err)
+ }
+ defer func() { _ = responder.Close(context.Background()) }()
+
+ log.Printf(" [*] Awaiting RPC requests")
+ select {}
+}
diff --git a/go-amqp/send.go b/go-amqp/send.go
new file mode 100644
index 00000000..5948c9b2
--- /dev/null
+++ b/go-amqp/send.go
@@ -0,0 +1,51 @@
+package main
+
+import (
+ "context"
+ "log"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "hello"})
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ publisher, err := conn.NewPublisher(ctx, &rmq.QueueAddress{Queue: "hello"}, nil)
+ if err != nil {
+ log.Panicf("Failed to create publisher: %v", err)
+ }
+ defer func() { _ = publisher.Close(context.Background()) }()
+
+ body := "Hello World!"
+ res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body)))
+ if err != nil {
+ log.Panicf("Failed to publish a message: %v", err)
+ }
+ switch res.Outcome.(type) {
+ case *rmq.StateAccepted:
+ case *rmq.StateRejected:
+ log.Fatalf("Message was rejected: %v", res.Outcome)
+ case *rmq.StateReleased:
+ log.Fatalf("Message was released: %v", res.Outcome)
+ case *rmq.StateModified:
+ log.Fatalf("Message was modified: %v", res.Outcome)
+ default:
+ log.Fatalf("Unexpected publish outcome: %v", res.Outcome)
+ }
+ log.Printf(" [x] Sent %s\n", body)
+}
diff --git a/go-amqp/test-tutorials.sh b/go-amqp/test-tutorials.sh
new file mode 100755
index 00000000..25f3d057
--- /dev/null
+++ b/go-amqp/test-tutorials.sh
@@ -0,0 +1,221 @@
+#!/usr/bin/env bash
+# End-to-end smoke tests for go-amqp tutorials.
+# Requires: bash, go, RabbitMQ 4.x on localhost:5672 (guest/guest).
+#
+# Usage:
+# ./go-amqp/test-tutorials.sh
+# cd go-amqp && ./test-tutorials.sh
+#
+# Environment:
+# SKIP_BROKER_CHECK=1 Skip TCP check on port 5672 (tests still need a broker).
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$ROOT"
+
+PASS=0
+FAIL=0
+
+pass() {
+ printf 'ok\t%s\n' "$1"
+ PASS=$((PASS + 1))
+}
+
+fail() {
+ printf 'FAIL\t%s\n' "$1" >&2
+ FAIL=$((FAIL + 1))
+}
+
+require_go() {
+ command -v go >/dev/null 2>&1 || {
+ echo "go is not on PATH" >&2
+ exit 1
+ }
+}
+
+check_broker() {
+ if command -v nc >/dev/null 2>&1; then
+ nc -z localhost 5672 2>/dev/null
+ return
+ fi
+ { echo >/dev/tcp/127.0.0.1/5672; } 2>/dev/null
+}
+
+require_broker() {
+ if [[ "${SKIP_BROKER_CHECK:-}" == "1" ]]; then
+ return 0
+ fi
+ if ! check_broker; then
+ echo "RabbitMQ does not appear to be reachable on localhost:5672" >&2
+ echo "Start the broker or set SKIP_BROKER_CHECK=1 to skip this check." >&2
+ exit 1
+ fi
+}
+
+assert_file_contains() {
+ local file=$1
+ local needle=$2
+ local msg=$3
+ if [[ -f "$file" ]] && grep -qF -- "$needle" "$file"; then
+ pass "$msg"
+ else
+ fail "$msg (log should contain: $needle)"
+ if [[ -f "$file" ]]; then
+ echo "--- $file (first 40 lines) ---" >&2
+ head -n 40 "$file" >&2
+ fi
+ fi
+}
+
+# Run command in background; sleep $secs; kill. Captures stdout/stderr to $log.
+run_timed() {
+ local log=$1
+ local secs=$2
+ shift 2
+ : >"$log"
+ "$@" >"$log" 2>&1 &
+ local pid=$!
+ sleep "$secs"
+ kill "$pid" 2>/dev/null || true
+ wait "$pid" 2>/dev/null || true
+}
+
+test_t1_hello() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ go run receive.go >"$recv_log" 2>&1 &
+ local rpid=$!
+ sleep 2
+ go run send.go >"$log" 2>&1
+ sleep 1
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T1 send publishes"
+ assert_file_contains "$recv_log" "Hello World" "T1 receive gets message"
+ rm -f "$log" "$recv_log"
+}
+
+test_t2_work_queues() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ go run worker.go >"$recv_log" 2>&1 &
+ local rpid=$!
+ sleep 2
+ go run new_task.go e2e.task.test >"$log" 2>&1
+ sleep 3
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T2 new_task publishes"
+ assert_file_contains "$recv_log" "e2e.task.test" "T2 worker receives task"
+ rm -f "$log" "$recv_log"
+}
+
+test_t3_pubsub() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ go run receive_logs.go >"$recv_log" 2>&1 &
+ local rpid=$!
+ sleep 2
+ go run emit_log.go e2e pubsub hello >"$log" 2>&1
+ sleep 2
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T3 emit_log publishes"
+ assert_file_contains "$recv_log" "e2e pubsub hello" "T3 receive_logs receives broadcast"
+ rm -f "$log" "$recv_log"
+}
+
+test_t4_routing() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ go run receive_logs_direct.go warn >"$recv_log" 2>&1 &
+ local rpid=$!
+ sleep 2
+ go run emit_log_direct.go warn "e2e routing" >"$log" 2>&1
+ sleep 2
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T4 emit_log_direct publishes"
+ assert_file_contains "$recv_log" "e2e routing" "T4 receive_logs_direct gets routed message"
+ rm -f "$log" "$recv_log"
+}
+
+test_t5_topics() {
+ local log recv_log
+ log=$(mktemp)
+ recv_log=$(mktemp)
+ go run receive_logs_topic.go "kern.*" >"$recv_log" 2>&1 &
+ local rpid=$!
+ sleep 2
+ go run emit_log_topic.go kern.info "e2e topic" >"$log" 2>&1
+ sleep 2
+ kill "$rpid" 2>/dev/null || true
+ wait "$rpid" 2>/dev/null || true
+ assert_file_contains "$log" "Sent" "T5 emit_log_topic publishes"
+ assert_file_contains "$recv_log" "e2e topic" "T5 receive_logs_topic matches binding"
+ rm -f "$log" "$recv_log"
+}
+
+test_t6_rpc() {
+ local log srv_log
+ log=$(mktemp)
+ srv_log=$(mktemp)
+ go run rpc_server.go >"$srv_log" 2>&1 &
+ local spid=$!
+ sleep 2
+ if ! go run rpc_client.go 10 >"$log" 2>&1; then
+ kill "$spid" 2>/dev/null || true
+ wait "$spid" 2>/dev/null || true
+ fail "T6 rpc_client process failed"
+ rm -f "$log" "$srv_log"
+ return
+ fi
+ kill "$spid" 2>/dev/null || true
+ wait "$spid" 2>/dev/null || true
+ assert_file_contains "$log" "Requesting fib(10)" "T6 rpc_client sends request"
+ assert_file_contains "$log" "Got 55" "T6 rpc_client gets fib(10)==55"
+ rm -f "$log" "$srv_log"
+}
+
+test_publisher_confirms() {
+ local log
+ log=$(mktemp)
+ run_timed "$log" 4 go run publisher_confirms.go
+ assert_file_contains "$log" "Confirmed" "publisher_confirms sees accepted publish"
+ assert_file_contains "$log" "Received a message" "publisher_confirms consumer sees message"
+ rm -f "$log"
+}
+
+test_rpc_amqp10() {
+ local log
+ log=$(mktemp)
+ run_timed "$log" 5 go run rpc_amqp10.go
+ assert_file_contains "$log" "pong" "rpc_amqp10 ping/pong (Direct Reply-To)"
+ rm -f "$log"
+}
+
+main() {
+ require_go
+ require_broker
+ echo "Running go-amqp tutorial smoke tests from $ROOT"
+ test_t1_hello
+ test_t2_work_queues
+ test_t3_pubsub
+ test_t4_routing
+ test_t5_topics
+ test_t6_rpc
+ test_publisher_confirms
+ test_rpc_amqp10
+ echo "----"
+ echo "Passed: $PASS Failed: $FAIL"
+ if [[ "$FAIL" -gt 0 ]]; then
+ exit 1
+ fi
+}
+
+main "$@"
diff --git a/go-amqp/worker.go b/go-amqp/worker.go
new file mode 100644
index 00000000..f3f52531
--- /dev/null
+++ b/go-amqp/worker.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "log"
+ "time"
+
+ rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
+)
+
+const brokerURI = "amqp://guest:guest@localhost:5672/"
+
+func main() {
+ ctx := context.Background()
+ env := rmq.NewEnvironment(brokerURI, nil)
+ conn, err := env.NewConnection(ctx)
+ if err != nil {
+ log.Panicf("Failed to connect to RabbitMQ: %v", err)
+ }
+ defer func() {
+ _ = env.CloseConnections(context.Background())
+ }()
+
+ _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "task_queue"})
+ if err != nil {
+ log.Panicf("Failed to declare a queue: %v", err)
+ }
+
+ consumer, err := conn.NewConsumer(ctx, "task_queue", &rmq.ConsumerOptions{InitialCredits: 1})
+ if err != nil {
+ log.Panicf("Failed to create consumer: %v", err)
+ }
+ defer func() { _ = consumer.Close(context.Background()) }()
+
+ log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
+ for {
+ delivery, err := consumer.Receive(ctx)
+ if err != nil {
+ if errors.Is(err, context.Canceled) {
+ return
+ }
+ log.Panicf("Failed to receive a message: %v", err)
+ }
+ msg := delivery.Message()
+ var payload []byte
+ if len(msg.Data) > 0 {
+ payload = msg.Data[0]
+ }
+ log.Printf("Received a message: %s", payload)
+ dotCount := bytes.Count(payload, []byte("."))
+ t := time.Duration(dotCount)
+ time.Sleep(t * time.Second)
+ log.Printf("Done")
+ err = delivery.Accept(ctx)
+ if err != nil {
+ log.Panicf("Failed to accept message: %v", err)
+ }
+ }
+}
diff --git a/java-amqp/.mvn/wrapper/maven-wrapper.jar b/java-amqp/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 00000000..cb28b0e3
Binary files /dev/null and b/java-amqp/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/java-amqp/.mvn/wrapper/maven-wrapper.properties b/java-amqp/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..346d645f
--- /dev/null
+++ b/java-amqp/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/java-amqp/README.md b/java-amqp/README.md
new file mode 100644
index 00000000..99a3c0e7
--- /dev/null
+++ b/java-amqp/README.md
@@ -0,0 +1,103 @@
+# Java code for RabbitMQ tutorials (AMQP 1.0 client)
+
+Here you can find Java examples for the [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html) using the [RabbitMQ AMQP 1.0 Java client](https://rabbitmq.github.io/rabbitmq-amqp-java-client/stable/htmlsingle/).
+
+You need a RabbitMQ node running locally. The client requires **RabbitMQ 4.0 or newer** with AMQP 1.0 (enabled by default on port **5672**). The examples use the default `guest` user. See also the [AMQP 1.0 client libraries overview](https://www.rabbitmq.com/client-libraries/amqp-client-libraries).
+
+This directory is a self-contained [Maven](https://maven.apache.org/) project. Use `./mvnw` on Unix-like systems or `mvnw.cmd` on Windows.
+
+Run integration tests with `./mvnw test` (they expect a broker on `localhost`).
+
+## Code
+
+#### Tutorial one: "Hello World!"
+
+```shell
+# terminal tab 1
+./mvnw compile exec:java -Dexec.mainClass=Recv
+
+# terminal tab 2
+./mvnw compile exec:java -Dexec.mainClass=Send
+```
+
+#### Tutorial two: Work Queues
+
+```shell
+# terminal tab 1
+./mvnw compile exec:java -Dexec.mainClass=Worker
+
+# terminal tab 2
+./mvnw compile exec:java -Dexec.mainClass=Worker
+
+# terminal tab 3
+./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="First Message"
+./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Second Message"
+./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Third Message"
+./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Fourth Message"
+./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Fifth Message"
+```
+
+#### Tutorial three: Publish/Subscribe
+
+```shell
+# terminal tab 1
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogs
+
+# terminal tab 2
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogs
+
+# terminal tab 3
+./mvnw compile exec:java -Dexec.mainClass=EmitLog
+```
+
+#### Tutorial four: Routing
+
+```shell
+# terminal tab 1
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsDirect -Dexec.args="warning error"
+
+# terminal tab 2
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsDirect -Dexec.args="info warning error"
+
+# terminal tab 3
+./mvnw compile exec:java -Dexec.mainClass=EmitLogDirect -Dexec.args="info Run. Run. Or it will explode."
+./mvnw compile exec:java -Dexec.mainClass=EmitLogDirect -Dexec.args="warning Run. Run. Or it will explode."
+./mvnw compile exec:java -Dexec.mainClass=EmitLogDirect -Dexec.args="error Run. Run. Or it will explode."
+```
+
+#### Tutorial five: Topics
+
+```shell
+# terminal tab 1
+# To receive all the logs:
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="#"
+
+# To receive all logs from the facility "kern":
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="kern.*"
+
+# Or if you want to hear only about "critical" logs:
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="*.critical"
+
+# You can create multiple bindings:
+./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="kern.* *.critical"
+
+# terminal tab 2
+# And to emit a log with a routing key "kern.critical" type:
+./mvnw compile exec:java -Dexec.mainClass=EmitLogTopic -Dexec.args="kern.critical A critical kernel error"
+```
+
+#### Tutorial six: RPC
+
+```shell
+# terminal tab 1
+./mvnw compile exec:java -Dexec.mainClass=RPCServer
+
+# terminal tab 2
+./mvnw compile exec:java -Dexec.mainClass=RPCClient
+```
+
+#### Tutorial seven: Publisher Confirms
+
+```shell
+./mvnw compile exec:java -Dexec.mainClass=PublisherConfirms
+```
diff --git a/java-amqp/mvnw b/java-amqp/mvnw
new file mode 100755
index 00000000..8d937f4c
--- /dev/null
+++ b/java-amqp/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/java-amqp/mvnw.cmd b/java-amqp/mvnw.cmd
new file mode 100644
index 00000000..c4586b56
--- /dev/null
+++ b/java-amqp/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/java-amqp/pom.xml b/java-amqp/pom.xml
new file mode 100644
index 00000000..c1826d5a
--- /dev/null
+++ b/java-amqp/pom.xml
@@ -0,0 +1,110 @@
+
+ 4.0.0
+
+ com.rabbitmq
+ rabbitmq-tutorial-amqp
+ 1.0-SNAPSHOT
+ jar
+
+ rabbitmq-tutorial-amqp
+ https://github.com/rabbitmq/rabbitmq-tutorials
+
+
+ UTF-8
+ 11
+ 11
+
+
+
+
+ com.rabbitmq.client
+ amqp-client
+ 0.10.0
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.17
+
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.17
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.14.3
+ test
+
+
+
+ org.assertj
+ assertj-core
+ 3.27.7
+ test
+
+
+
+
+
+
+
+ maven-compiler-plugin
+ 3.15.0
+
+ 11
+
+
+
+ maven-surefire-plugin
+ 3.5.5
+
+ ${test-arguments}
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.5.0
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.5.0
+
+ ${project.build.sourceEncoding}
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.6.2
+
+ false
+
+
+
+
+
+
+
+
+ jvm-test-arguments-java-21-and-more
+
+ [21,)
+
+
+ -XX:+EnableDynamicAgentLoading
+
+
+
+
+
+
diff --git a/java-amqp/src/main/java/EmitLog.java b/java-amqp/src/main/java/EmitLog.java
new file mode 100644
index 00000000..f7c28ff5
--- /dev/null
+++ b/java-amqp/src/main/java/EmitLog.java
@@ -0,0 +1,39 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Publisher;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class EmitLog {
+
+ private static final String EXCHANGE_NAME = "logs";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ try (Management management = connection.management()) {
+ management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.FANOUT).declare();
+ }
+ try (Publisher publisher = connection.publisherBuilder().exchange(EXCHANGE_NAME).build()) {
+ String message = argv.length < 1 ? "info: Hello World!" : String.join(" ", argv);
+ Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8));
+ CountDownLatch confirmed = new CountDownLatch(1);
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ confirmed.countDown();
+ }
+ });
+ if (!confirmed.await(30, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Publish was not confirmed in time");
+ }
+ System.out.println(" [x] Sent '" + message + "'");
+ }
+ } finally {
+ environment.close();
+ }
+ }
+}
diff --git a/java-amqp/src/main/java/EmitLogDirect.java b/java-amqp/src/main/java/EmitLogDirect.java
new file mode 100644
index 00000000..1e053ea9
--- /dev/null
+++ b/java-amqp/src/main/java/EmitLogDirect.java
@@ -0,0 +1,69 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Publisher;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class EmitLogDirect {
+
+ private static final String EXCHANGE_NAME = "direct_logs";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ try (Management management = connection.management()) {
+ management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.DIRECT).declare();
+ }
+ String severity = getSeverity(argv);
+ try (Publisher publisher = connection.publisherBuilder().exchange(EXCHANGE_NAME).key(severity).build()) {
+ String message = getMessage(argv);
+ Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8));
+ CountDownLatch confirmed = new CountDownLatch(1);
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ confirmed.countDown();
+ }
+ });
+ if (!confirmed.await(30, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Publish was not confirmed in time");
+ }
+ System.out.println(" [x] Sent '" + severity + "':'" + message + "'");
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ private static String getSeverity(String[] strings) {
+ if (strings.length < 1) {
+ return "info";
+ }
+ return strings[0];
+ }
+
+ private static String getMessage(String[] strings) {
+ if (strings.length < 2) {
+ return "Hello World!";
+ }
+ return joinStrings(strings, " ", 1);
+ }
+
+ private static String joinStrings(String[] strings, String delimiter, int startIndex) {
+ int length = strings.length;
+ if (length == 0) {
+ return "";
+ }
+ if (length <= startIndex) {
+ return "";
+ }
+ StringBuilder words = new StringBuilder(strings[startIndex]);
+ for (int i = startIndex + 1; i < length; i++) {
+ words.append(delimiter).append(strings[i]);
+ }
+ return words.toString();
+ }
+}
diff --git a/java-amqp/src/main/java/EmitLogTopic.java b/java-amqp/src/main/java/EmitLogTopic.java
new file mode 100644
index 00000000..c8001017
--- /dev/null
+++ b/java-amqp/src/main/java/EmitLogTopic.java
@@ -0,0 +1,69 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Publisher;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class EmitLogTopic {
+
+ private static final String EXCHANGE_NAME = "topic_logs";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ try (Management management = connection.management()) {
+ management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.TOPIC).declare();
+ }
+ String routingKey = getRouting(argv);
+ try (Publisher publisher = connection.publisherBuilder().exchange(EXCHANGE_NAME).key(routingKey).build()) {
+ String message = getMessage(argv);
+ Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8));
+ CountDownLatch confirmed = new CountDownLatch(1);
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ confirmed.countDown();
+ }
+ });
+ if (!confirmed.await(30, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Publish was not confirmed in time");
+ }
+ System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ private static String getRouting(String[] strings) {
+ if (strings.length < 1) {
+ return "anonymous.info";
+ }
+ return strings[0];
+ }
+
+ private static String getMessage(String[] strings) {
+ if (strings.length < 2) {
+ return "Hello World!";
+ }
+ return joinStrings(strings, " ", 1);
+ }
+
+ private static String joinStrings(String[] strings, String delimiter, int startIndex) {
+ int length = strings.length;
+ if (length == 0) {
+ return "";
+ }
+ if (length < startIndex) {
+ return "";
+ }
+ StringBuilder words = new StringBuilder(strings[startIndex]);
+ for (int i = startIndex + 1; i < length; i++) {
+ words.append(delimiter).append(strings[i]);
+ }
+ return words.toString();
+ }
+}
diff --git a/java-amqp/src/main/java/NewTask.java b/java-amqp/src/main/java/NewTask.java
new file mode 100644
index 00000000..80aee9fb
--- /dev/null
+++ b/java-amqp/src/main/java/NewTask.java
@@ -0,0 +1,42 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Publisher;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class NewTask {
+
+ private static final String TASK_QUEUE_NAME = "task_queue";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ try (Management management = connection.management()) {
+ management.queue(TASK_QUEUE_NAME).quorum().queue().declare();
+ // Un-comment the following line to declare a Classic Queue
+ // Comment the previous queue declaration, if you un-comment below
+ // management.queue(TASK_QUEUE_NAME).classic().queue().declare();
+ }
+ try (Publisher publisher = connection.publisherBuilder().queue(TASK_QUEUE_NAME).build()) {
+ String message = String.join(" ", argv);
+ Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8)).durable(true);
+ CountDownLatch confirmed = new CountDownLatch(1);
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ confirmed.countDown();
+ }
+ });
+ if (!confirmed.await(30, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Publish was not confirmed in time");
+ }
+ System.out.println(" [x] Sent '" + message + "'");
+ }
+ } finally {
+ environment.close();
+ }
+ }
+}
diff --git a/java-amqp/src/main/java/PublisherConfirms.java b/java-amqp/src/main/java/PublisherConfirms.java
new file mode 100644
index 00000000..ca707c1b
--- /dev/null
+++ b/java-amqp/src/main/java/PublisherConfirms.java
@@ -0,0 +1,132 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Publisher;
+
+import java.time.Duration;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * AMQP 1.0 clients publish with broker feedback in the {@link Publisher.Callback} (accepted / rejected / released).
+ * This is the counterpart to AMQP 0.9.1 publisher confirms.
+ */
+public class PublisherConfirms {
+
+ static final int MESSAGE_COUNT = 50_000;
+
+ public static void main(String[] args) throws Exception {
+ publishMessagesIndividually();
+ publishMessagesInBatch();
+ publishMessagesWithOutstandingTracking();
+ }
+
+ static void publishMessagesIndividually() throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue = UUID.randomUUID().toString();
+ try (Management management = connection.management()) {
+ management.queue().name(queue).exclusive(true).autoDelete(true).declare();
+ }
+ try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) {
+ long start = System.nanoTime();
+ for (int i = 0; i < MESSAGE_COUNT; i++) {
+ String body = String.valueOf(i);
+ Message msg = publisher.message(body.getBytes());
+ CountDownLatch latch = new CountDownLatch(1);
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ latch.countDown();
+ }
+ });
+ if (!latch.await(30, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Confirm not received for message " + i);
+ }
+ }
+ long end = System.nanoTime();
+ System.out.format("Published %,d messages individually in %,d ms%n", MESSAGE_COUNT,
+ Duration.ofNanos(end - start).toMillis());
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ static void publishMessagesInBatch() throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue = UUID.randomUUID().toString();
+ try (Management management = connection.management()) {
+ management.queue().name(queue).exclusive(true).autoDelete(true).declare();
+ }
+ try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) {
+ int batchSize = 100;
+ long start = System.nanoTime();
+ int i = 0;
+ while (i < MESSAGE_COUNT) {
+ int n = Math.min(batchSize, MESSAGE_COUNT - i);
+ CountDownLatch batchLatch = new CountDownLatch(n);
+ for (int j = 0; j < n; j++) {
+ String body = String.valueOf(i + j);
+ Message msg = publisher.message(body.getBytes());
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ batchLatch.countDown();
+ }
+ });
+ }
+ if (!batchLatch.await(60, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Batch confirm incomplete at offset " + i);
+ }
+ i += n;
+ }
+ long end = System.nanoTime();
+ System.out.format("Published %,d messages in batch in %,d ms%n", MESSAGE_COUNT,
+ Duration.ofNanos(end - start).toMillis());
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ static void publishMessagesWithOutstandingTracking() throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue = UUID.randomUUID().toString();
+ try (Management management = connection.management()) {
+ management.queue().name(queue).exclusive(true).autoDelete(true).declare();
+ }
+ final int maxOutstanding = 1000;
+ try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) {
+ AtomicInteger outstanding = new AtomicInteger(0);
+ CountDownLatch allDone = new CountDownLatch(MESSAGE_COUNT);
+ long start = System.nanoTime();
+ for (int i = 0; i < MESSAGE_COUNT; i++) {
+ while (outstanding.get() >= maxOutstanding) {
+ Thread.sleep(1);
+ }
+ outstanding.incrementAndGet();
+ String body = String.valueOf(i);
+ Message msg = publisher.message(body.getBytes());
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ outstanding.decrementAndGet();
+ allDone.countDown();
+ }
+ });
+ }
+ if (!allDone.await(120, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Not all messages confirmed");
+ }
+ long end = System.nanoTime();
+ System.out.format("Published %,d messages with outstanding window in %,d ms%n", MESSAGE_COUNT,
+ Duration.ofNanos(end - start).toMillis());
+ }
+ } finally {
+ environment.close();
+ }
+ }
+}
diff --git a/java-amqp/src/main/java/RPCClient.java b/java-amqp/src/main/java/RPCClient.java
new file mode 100644
index 00000000..c1093404
--- /dev/null
+++ b/java-amqp/src/main/java/RPCClient.java
@@ -0,0 +1,30 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Requester;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+public class RPCClient {
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build();
+ Requester requester = connection.requesterBuilder()
+ .requestAddress().queue("rpc_queue")
+ .requester()
+ .build()) {
+ for (int i = 0; i < 32; i++) {
+ String iStr = Integer.toString(i);
+ System.out.println(" [x] Requesting fib(" + iStr + ")");
+ Message request = requester.message(iStr.getBytes(StandardCharsets.UTF_8));
+ Message reply = requester.publish(request).get(30, TimeUnit.SECONDS);
+ String response = new String(reply.body(), StandardCharsets.UTF_8);
+ System.out.println(" [.] Got '" + response + "'");
+ }
+ } finally {
+ environment.close();
+ }
+ }
+}
diff --git a/java-amqp/src/main/java/RPCServer.java b/java-amqp/src/main/java/RPCServer.java
new file mode 100644
index 00000000..718ba625
--- /dev/null
+++ b/java-amqp/src/main/java/RPCServer.java
@@ -0,0 +1,56 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Responder;
+
+import java.nio.charset.StandardCharsets;
+
+public class RPCServer {
+
+ private static final String RPC_QUEUE_NAME = "rpc_queue";
+
+ private static int fib(int n) {
+ if (n == 0) {
+ return 0;
+ }
+ if (n == 1) {
+ return 1;
+ }
+ return fib(n - 1) + fib(n - 2);
+ }
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build();
+ try (Management management = connection.management()) {
+ management.queue().name(RPC_QUEUE_NAME).quorum().queue().declare();
+ management.queuePurge(RPC_QUEUE_NAME);
+ }
+
+ System.out.println(" [x] Awaiting RPC requests");
+
+ Responder responder = connection.responderBuilder()
+ .requestQueue(RPC_QUEUE_NAME)
+ .handler((ctx, req) -> {
+ String response = "";
+ try {
+ String message = new String(req.body(), StandardCharsets.UTF_8);
+ int n = Integer.parseInt(message);
+ System.out.println(" [.] fib(" + message + ")");
+ response += fib(n);
+ } catch (RuntimeException e) {
+ System.out.println(" [.] " + e);
+ }
+ return ctx.message(response.getBytes(StandardCharsets.UTF_8));
+ })
+ .build();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ responder.close();
+ connection.close();
+ environment.close();
+ }));
+
+ Thread.sleep(Long.MAX_VALUE);
+ }
+}
diff --git a/java-amqp/src/main/java/ReceiveLogs.java b/java-amqp/src/main/java/ReceiveLogs.java
new file mode 100644
index 00000000..6c596427
--- /dev/null
+++ b/java-amqp/src/main/java/ReceiveLogs.java
@@ -0,0 +1,48 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Consumer;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Management.QueueInfo;
+
+import java.nio.charset.StandardCharsets;
+
+public class ReceiveLogs {
+
+ private static final String EXCHANGE_NAME = "logs";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build();
+
+ String queueName;
+ try (Management management = connection.management()) {
+ management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.FANOUT).declare();
+ QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare();
+ queueName = q.name();
+ management.binding()
+ .sourceExchange(EXCHANGE_NAME)
+ .destinationQueue(queueName)
+ .key("")
+ .bind();
+ }
+
+ System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
+
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queueName)
+ .messageHandler((ctx, message) -> {
+ String body = new String(message.body(), StandardCharsets.UTF_8);
+ System.out.println(" [x] Received '" + body + "'");
+ ctx.accept();
+ })
+ .build();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ consumer.close();
+ connection.close();
+ environment.close();
+ }));
+
+ Thread.sleep(Long.MAX_VALUE);
+ }
+}
diff --git a/java-amqp/src/main/java/ReceiveLogsDirect.java b/java-amqp/src/main/java/ReceiveLogsDirect.java
new file mode 100644
index 00000000..5591baea
--- /dev/null
+++ b/java-amqp/src/main/java/ReceiveLogsDirect.java
@@ -0,0 +1,66 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Consumer;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Management.QueueInfo;
+import com.rabbitmq.client.amqp.Message;
+
+import java.nio.charset.StandardCharsets;
+
+public class ReceiveLogsDirect {
+
+ private static final String EXCHANGE_NAME = "direct_logs";
+
+ public static void main(String[] argv) throws Exception {
+ if (argv.length < 1) {
+ System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]");
+ System.exit(1);
+ }
+
+ Environment environment = TutorialSupport.newEnvironment();
+ Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build();
+
+ String queueName;
+ try (Management management = connection.management()) {
+ management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.DIRECT).declare();
+ QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare();
+ queueName = q.name();
+ for (String severity : argv) {
+ management.binding()
+ .sourceExchange(EXCHANGE_NAME)
+ .destinationQueue(queueName)
+ .key(severity)
+ .bind();
+ }
+ }
+
+ System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
+
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queueName)
+ .messageHandler((ctx, message) -> {
+ String body = new String(message.body(), StandardCharsets.UTF_8);
+ String routingKey = routingKey(message);
+ System.out.println(" [x] Received '" + routingKey + "':'" + body + "'");
+ ctx.accept();
+ })
+ .build();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ consumer.close();
+ connection.close();
+ environment.close();
+ }));
+
+ Thread.sleep(Long.MAX_VALUE);
+ }
+
+ private static String routingKey(Message message) {
+ Object rk = message.annotation("x-routing-key");
+ if (rk != null) {
+ return rk.toString();
+ }
+ String subject = message.subject();
+ return subject != null ? subject : "";
+ }
+}
diff --git a/java-amqp/src/main/java/ReceiveLogsTopic.java b/java-amqp/src/main/java/ReceiveLogsTopic.java
new file mode 100644
index 00000000..306baabd
--- /dev/null
+++ b/java-amqp/src/main/java/ReceiveLogsTopic.java
@@ -0,0 +1,66 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Consumer;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Management.QueueInfo;
+import com.rabbitmq.client.amqp.Message;
+
+import java.nio.charset.StandardCharsets;
+
+public class ReceiveLogsTopic {
+
+ private static final String EXCHANGE_NAME = "topic_logs";
+
+ public static void main(String[] argv) throws Exception {
+ if (argv.length < 1) {
+ System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
+ System.exit(1);
+ }
+
+ Environment environment = TutorialSupport.newEnvironment();
+ Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build();
+
+ String queueName;
+ try (Management management = connection.management()) {
+ management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.TOPIC).declare();
+ QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare();
+ queueName = q.name();
+ for (String bindingKey : argv) {
+ management.binding()
+ .sourceExchange(EXCHANGE_NAME)
+ .destinationQueue(queueName)
+ .key(bindingKey)
+ .bind();
+ }
+ }
+
+ System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
+
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queueName)
+ .messageHandler((ctx, message) -> {
+ String body = new String(message.body(), StandardCharsets.UTF_8);
+ String routingKey = routingKey(message);
+ System.out.println(" [x] Received '" + routingKey + "':'" + body + "'");
+ ctx.accept();
+ })
+ .build();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ consumer.close();
+ connection.close();
+ environment.close();
+ }));
+
+ Thread.sleep(Long.MAX_VALUE);
+ }
+
+ private static String routingKey(Message message) {
+ Object rk = message.annotation("x-routing-key");
+ if (rk != null) {
+ return rk.toString();
+ }
+ String subject = message.subject();
+ return subject != null ? subject : "";
+ }
+}
diff --git a/java-amqp/src/main/java/Recv.java b/java-amqp/src/main/java/Recv.java
new file mode 100644
index 00000000..7bea54a2
--- /dev/null
+++ b/java-amqp/src/main/java/Recv.java
@@ -0,0 +1,38 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Consumer;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+
+import java.nio.charset.StandardCharsets;
+
+public class Recv {
+
+ private static final String QUEUE_NAME = "hello";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build();
+ try (Management management = connection.management()) {
+ management.queue().name(QUEUE_NAME).quorum().queue().declare();
+ }
+
+ System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
+
+ Consumer consumer = connection.consumerBuilder()
+ .queue(QUEUE_NAME)
+ .messageHandler((ctx, message) -> {
+ String body = new String(message.body(), StandardCharsets.UTF_8);
+ System.out.println(" [x] Received '" + body + "'");
+ ctx.accept();
+ })
+ .build();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ consumer.close();
+ connection.close();
+ environment.close();
+ }));
+
+ Thread.sleep(Long.MAX_VALUE);
+ }
+}
diff --git a/java-amqp/src/main/java/Send.java b/java-amqp/src/main/java/Send.java
new file mode 100644
index 00000000..3a3410ca
--- /dev/null
+++ b/java-amqp/src/main/java/Send.java
@@ -0,0 +1,39 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Publisher;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class Send {
+
+ private static final String QUEUE_NAME = "hello";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ try (Management management = connection.management()) {
+ management.queue().name(QUEUE_NAME).quorum().queue().declare();
+ }
+ try (Publisher publisher = connection.publisherBuilder().queue(QUEUE_NAME).build()) {
+ String message = "Hello World!";
+ Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8));
+ CountDownLatch confirmed = new CountDownLatch(1);
+ publisher.publish(msg, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ confirmed.countDown();
+ }
+ });
+ if (!confirmed.await(30, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Publish was not confirmed in time");
+ }
+ System.out.println(" [x] Sent '" + message + "'");
+ }
+ } finally {
+ environment.close();
+ }
+ }
+}
diff --git a/java-amqp/src/main/java/TutorialSupport.java b/java-amqp/src/main/java/TutorialSupport.java
new file mode 100644
index 00000000..5eb5fcd8
--- /dev/null
+++ b/java-amqp/src/main/java/TutorialSupport.java
@@ -0,0 +1,17 @@
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder;
+
+/**
+ * Shared connection defaults for the AMQP 1.0 tutorials (guest on localhost, default port).
+ */
+final class TutorialSupport {
+
+ static final String BROKER_URI = "amqp://guest:guest@localhost:5672/%2f";
+
+ private TutorialSupport() {
+ }
+
+ static Environment newEnvironment() {
+ return new AmqpEnvironmentBuilder().build();
+ }
+}
diff --git a/java-amqp/src/main/java/Worker.java b/java-amqp/src/main/java/Worker.java
new file mode 100644
index 00000000..164d7d4f
--- /dev/null
+++ b/java-amqp/src/main/java/Worker.java
@@ -0,0 +1,56 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Consumer;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+
+import java.nio.charset.StandardCharsets;
+
+public class Worker {
+
+ private static final String TASK_QUEUE_NAME = "task_queue";
+
+ public static void main(String[] argv) throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build();
+ try (Management management = connection.management()) {
+ management.queue().name(TASK_QUEUE_NAME).quorum().queue().declare();
+ }
+
+ System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
+
+ Consumer consumer = connection.consumerBuilder()
+ .queue(TASK_QUEUE_NAME)
+ .initialCredits(1)
+ .messageHandler((ctx, message) -> {
+ String body = new String(message.body(), StandardCharsets.UTF_8);
+ System.out.println(" [x] Received '" + body + "'");
+ try {
+ doWork(body);
+ } finally {
+ System.out.println(" [x] Done");
+ ctx.accept();
+ }
+ })
+ .build();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ consumer.close();
+ connection.close();
+ environment.close();
+ }));
+
+ Thread.sleep(Long.MAX_VALUE);
+ }
+
+ private static void doWork(String task) {
+ for (char ch : task.toCharArray()) {
+ if (ch == '.') {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignored) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+}
diff --git a/java-amqp/src/test/java/Amqp10TutorialIntegrationTest.java b/java-amqp/src/test/java/Amqp10TutorialIntegrationTest.java
new file mode 100644
index 00000000..a1b3c461
--- /dev/null
+++ b/java-amqp/src/test/java/Amqp10TutorialIntegrationTest.java
@@ -0,0 +1,306 @@
+import com.rabbitmq.client.amqp.Connection;
+import com.rabbitmq.client.amqp.Consumer;
+import com.rabbitmq.client.amqp.Environment;
+import com.rabbitmq.client.amqp.Management;
+import com.rabbitmq.client.amqp.Management.QueueInfo;
+import com.rabbitmq.client.amqp.Message;
+import com.rabbitmq.client.amqp.Publisher;
+import com.rabbitmq.client.amqp.Requester;
+import com.rabbitmq.client.amqp.Responder;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests against a RabbitMQ node on localhost (same requirement as running the tutorials).
+ */
+class Amqp10TutorialIntegrationTest {
+
+ @Test
+ @Timeout(60)
+ void tutorial1_helloWorld() throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue = "hello_it_" + UUID.randomUUID();
+ try (Management management = connection.management()) {
+ management.queue().name(queue).quorum().queue().declare();
+ }
+ BlockingQueue received = new LinkedBlockingQueue<>();
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queue)
+ .messageHandler((ctx, msg) -> {
+ received.offer(new String(msg.body(), StandardCharsets.UTF_8));
+ ctx.accept();
+ })
+ .build();
+ try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) {
+ CountDownLatch ok = new CountDownLatch(1);
+ Message m = publisher.message("Hello World!".getBytes(StandardCharsets.UTF_8));
+ publisher.publish(m, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ ok.countDown();
+ }
+ });
+ assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue();
+ }
+ assertThat(received.poll(30, TimeUnit.SECONDS)).isEqualTo("Hello World!");
+ consumer.close();
+ try (Management management = connection.management()) {
+ management.queueDelete(queue);
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ @Test
+ @Timeout(120)
+ void tutorial2_workQueueFairDispatch() throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue = "task_it_" + UUID.randomUUID();
+ try (Management management = connection.management()) {
+ management.queue().name(queue).quorum().queue().declare();
+ }
+ CountDownLatch processed = new CountDownLatch(3);
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queue)
+ .initialCredits(1)
+ .messageHandler((ctx, msg) -> {
+ processed.countDown();
+ ctx.accept();
+ })
+ .build();
+ try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) {
+ for (int k = 0; k < 3; k++) {
+ CountDownLatch ok = new CountDownLatch(1);
+ Message m = publisher.message(("m" + k).getBytes(StandardCharsets.UTF_8));
+ publisher.publish(m, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ ok.countDown();
+ }
+ });
+ assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue();
+ }
+ }
+ assertThat(processed.await(30, TimeUnit.SECONDS)).isTrue();
+ consumer.close();
+ try (Management management = connection.management()) {
+ management.queueDelete(queue);
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ @Test
+ @Timeout(60)
+ void tutorial3_publishSubscribe() throws Exception {
+ String ex = "logs_it_" + UUID.randomUUID();
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue;
+ try (Management management = connection.management()) {
+ management.exchange().name(ex).type(Management.ExchangeType.FANOUT).declare();
+ QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare();
+ queue = q.name();
+ management.binding().sourceExchange(ex).destinationQueue(queue).key("").bind();
+ }
+ BlockingQueue received = new LinkedBlockingQueue<>();
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queue)
+ .messageHandler((ctx, msg) -> {
+ received.offer(new String(msg.body(), StandardCharsets.UTF_8));
+ ctx.accept();
+ })
+ .build();
+ try (Publisher publisher = connection.publisherBuilder().exchange(ex).build()) {
+ CountDownLatch ok = new CountDownLatch(1);
+ Message m = publisher.message("broadcast".getBytes(StandardCharsets.UTF_8));
+ publisher.publish(m, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ ok.countDown();
+ }
+ });
+ assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue();
+ }
+ assertThat(received.poll(30, TimeUnit.SECONDS)).isEqualTo("broadcast");
+ consumer.close();
+ try (Management management = connection.management()) {
+ management.exchangeDelete(ex);
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ @Test
+ @Timeout(60)
+ void tutorial4_routing() throws Exception {
+ String ex = "direct_it_" + UUID.randomUUID();
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue;
+ try (Management management = connection.management()) {
+ management.exchange().name(ex).type(Management.ExchangeType.DIRECT).declare();
+ QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare();
+ queue = q.name();
+ management.binding().sourceExchange(ex).destinationQueue(queue).key("info").bind();
+ }
+ BlockingQueue keys = new LinkedBlockingQueue<>();
+ BlockingQueue bodies = new LinkedBlockingQueue<>();
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queue)
+ .messageHandler((ctx, msg) -> {
+ Object ann = msg.annotation("x-routing-key");
+ String rk = ann != null ? ann.toString() : (msg.subject() != null ? msg.subject() : "");
+ keys.offer(rk);
+ bodies.offer(new String(msg.body(), StandardCharsets.UTF_8));
+ ctx.accept();
+ })
+ .build();
+ try (Publisher publisher = connection.publisherBuilder().exchange(ex).key("info").build()) {
+ CountDownLatch ok = new CountDownLatch(1);
+ Message m = publisher.message("Hello".getBytes(StandardCharsets.UTF_8));
+ publisher.publish(m, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ ok.countDown();
+ }
+ });
+ assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue();
+ }
+ assertThat(keys.poll(30, TimeUnit.SECONDS)).isEqualTo("info");
+ assertThat(bodies.poll(30, TimeUnit.SECONDS)).isEqualTo("Hello");
+ consumer.close();
+ try (Management management = connection.management()) {
+ management.exchangeDelete(ex);
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ @Test
+ @Timeout(60)
+ void tutorial5_topics() throws Exception {
+ String ex = "topic_it_" + UUID.randomUUID();
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue;
+ try (Management management = connection.management()) {
+ management.exchange().name(ex).type(Management.ExchangeType.TOPIC).declare();
+ QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare();
+ queue = q.name();
+ management.binding().sourceExchange(ex).destinationQueue(queue).key("*.critical").bind();
+ }
+ BlockingQueue keys = new LinkedBlockingQueue<>();
+ Consumer consumer = connection.consumerBuilder()
+ .queue(queue)
+ .messageHandler((ctx, msg) -> {
+ Object ann = msg.annotation("x-routing-key");
+ keys.offer(ann != null ? ann.toString() : (msg.subject() != null ? msg.subject() : ""));
+ ctx.accept();
+ })
+ .build();
+ try (Publisher publisher = connection.publisherBuilder().exchange(ex).key("kern.critical").build()) {
+ CountDownLatch ok = new CountDownLatch(1);
+ Message m = publisher.message("A critical kernel error".getBytes(StandardCharsets.UTF_8));
+ publisher.publish(m, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ ok.countDown();
+ }
+ });
+ assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue();
+ }
+ assertThat(keys.poll(30, TimeUnit.SECONDS)).isEqualTo("kern.critical");
+ consumer.close();
+ try (Management management = connection.management()) {
+ management.exchangeDelete(ex);
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ @Test
+ @Timeout(60)
+ void tutorial6_rpc() throws Exception {
+ String queue = "rpc_it_" + UUID.randomUUID();
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ try (Management management = connection.management()) {
+ management.queue().name(queue).quorum().queue().declare();
+ }
+ Responder responder = connection.responderBuilder()
+ .requestQueue(queue)
+ .handler((ctx, req) -> {
+ int n = Integer.parseInt(new String(req.body(), StandardCharsets.UTF_8));
+ int fib = fib(n);
+ return ctx.message(String.valueOf(fib).getBytes(StandardCharsets.UTF_8));
+ })
+ .build();
+ try (Requester requester = connection.requesterBuilder()
+ .requestAddress().queue(queue)
+ .requester()
+ .build()) {
+ Message reply = requester.publish(requester.message("10".getBytes(StandardCharsets.UTF_8)))
+ .get(30, TimeUnit.SECONDS);
+ assertThat(new String(reply.body(), StandardCharsets.UTF_8)).isEqualTo("55");
+ } finally {
+ responder.close();
+ }
+ try (Management management = connection.management()) {
+ management.queueDelete(queue);
+ }
+ } finally {
+ environment.close();
+ }
+ }
+
+ private static int fib(int n) {
+ if (n == 0) {
+ return 0;
+ }
+ if (n == 1) {
+ return 1;
+ }
+ return fib(n - 1) + fib(n - 2);
+ }
+
+ @Test
+ @Timeout(120)
+ void tutorial7_publisherConfirms() throws Exception {
+ Environment environment = TutorialSupport.newEnvironment();
+ try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) {
+ String queue = UUID.randomUUID().toString();
+ try (Management management = connection.management()) {
+ management.queue().name(queue).exclusive(true).autoDelete(true).declare();
+ }
+ int nMsg = 500;
+ CountDownLatch confirmed = new CountDownLatch(nMsg);
+ try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) {
+ for (int i = 0; i < nMsg; i++) {
+ String body = String.valueOf(i);
+ Message m = publisher.message(body.getBytes(StandardCharsets.UTF_8));
+ publisher.publish(m, ctx -> {
+ if (ctx.status() == Publisher.Status.ACCEPTED) {
+ confirmed.countDown();
+ }
+ });
+ }
+ assertThat(confirmed.await(60, TimeUnit.SECONDS)).isTrue();
+ }
+ } finally {
+ environment.close();
+ }
+ }
+}