| | | 1 | | using RabbitMQ.Client; |
| | | 2 | | using RabbitMQ.Client.Events; |
| | | 3 | | |
| | | 4 | | namespace AsyncResponse.Transports.RabbitMQ; |
| | | 5 | | |
| | | 6 | | internal interface IRabbitMqConnectionFactory |
| | | 7 | | { |
| | | 8 | | Task<IRabbitMqConnection> CreateConnectionAsync(CancellationToken cancellationToken = default); |
| | | 9 | | } |
| | | 10 | | |
| | 3 | 11 | | internal sealed class RabbitMqConnectionFactoryAdapter( |
| | 3 | 12 | | RabbitMqAsyncResponseOptions options) : IRabbitMqConnectionFactory |
| | | 13 | | { |
| | | 14 | | /// <summary>Creates the requested resource.</summary> |
| | | 15 | | public async Task<IRabbitMqConnection> CreateConnectionAsync(CancellationToken cancellationToken = default) |
| | | 16 | | { |
| | 3 | 17 | | var factory = CreateFactory(options); |
| | 1 | 18 | | var connection = await factory.CreateConnectionAsync(cancellationToken).ConfigureAwait(false); |
| | 1 | 19 | | return new RabbitMqConnectionAdapter(connection); |
| | 1 | 20 | | } |
| | | 21 | | |
| | | 22 | | private static ConnectionFactory CreateFactory(RabbitMqAsyncResponseOptions options) |
| | | 23 | | { |
| | 3 | 24 | | var factory = new ConnectionFactory |
| | 3 | 25 | | { |
| | 3 | 26 | | AutomaticRecoveryEnabled = options.AutomaticRecoveryEnabled, |
| | 3 | 27 | | TopologyRecoveryEnabled = options.TopologyRecoveryEnabled, |
| | 3 | 28 | | NetworkRecoveryInterval = options.NetworkRecoveryInterval, |
| | 3 | 29 | | RequestedHeartbeat = options.RequestedHeartbeat, |
| | 3 | 30 | | ClientProvidedName = options.ClientProvidedName, |
| | 3 | 31 | | ConsumerDispatchConcurrency = 1 |
| | 3 | 32 | | }; |
| | | 33 | | |
| | 3 | 34 | | if (!string.IsNullOrWhiteSpace(options.ConnectionString)) |
| | | 35 | | { |
| | 3 | 36 | | factory.Uri = new Uri(options.ConnectionString); |
| | 3 | 37 | | return factory; |
| | | 38 | | } |
| | | 39 | | |
| | 2 | 40 | | factory.HostName = RabbitMqOptionsValidator.Required(options.HostName, nameof(options.HostName)); |
| | 2 | 41 | | factory.Port = options.Port; |
| | 2 | 42 | | factory.VirtualHost = RabbitMqOptionsValidator.Required(options.VirtualHost, nameof(options.VirtualHost)); |
| | 2 | 43 | | factory.UserName = RabbitMqOptionsValidator.Required(options.UserName, nameof(options.UserName)); |
| | 2 | 44 | | factory.Password = options.Password; |
| | 3 | 45 | | return factory; |
| | | 46 | | } |
| | | 47 | | } |
| | | 48 | | |
| | | 49 | | internal interface IRabbitMqConnection : IAsyncDisposable |
| | | 50 | | { |
| | | 51 | | Task<IRabbitMqChannel> CreateChannelAsync(bool publisherConfirmations = false, CancellationToken cancellationToken = |
| | | 52 | | Task CloseAsync(TimeSpan timeout, CancellationToken cancellationToken = default); |
| | | 53 | | } |
| | | 54 | | |
| | | 55 | | internal sealed class RabbitMqConnectionAdapter(IConnection inner) : IRabbitMqConnection |
| | | 56 | | { |
| | | 57 | | /// <summary>Creates the requested resource.</summary> |
| | | 58 | | public async Task<IRabbitMqChannel> CreateChannelAsync(bool publisherConfirmations = false, CancellationToken cancel |
| | | 59 | | { |
| | | 60 | | // Enabling publisher confirmations with tracking makes BasicPublishAsync await the broker |
| | | 61 | | // acknowledgement and throw on a nack or an unroutable (mandatory) return, so a worker job is |
| | | 62 | | // never silently lost. Consumer channels pass false and keep the lighter default behavior. |
| | | 63 | | var options = publisherConfirmations |
| | | 64 | | ? new CreateChannelOptions(publisherConfirmationsEnabled: true, publisherConfirmationTrackingEnabled: true) |
| | | 65 | | : null; |
| | | 66 | | var channel = await inner.CreateChannelAsync(options, cancellationToken).ConfigureAwait(false); |
| | | 67 | | return new RabbitMqChannelAdapter(channel); |
| | | 68 | | } |
| | | 69 | | |
| | | 70 | | /// <summary>Runs the CloseAsync operation.</summary> |
| | | 71 | | public Task CloseAsync(TimeSpan timeout, CancellationToken cancellationToken = default) |
| | | 72 | | => inner.CloseAsync(200, "AsyncResponse shutdown", timeout, abort: false, cancellationToken); |
| | | 73 | | |
| | | 74 | | /// <summary>Releases resources held by this instance.</summary> |
| | | 75 | | public ValueTask DisposeAsync() => inner.DisposeAsync(); |
| | | 76 | | } |
| | | 77 | | |
| | | 78 | | internal interface IRabbitMqChannel : IAsyncDisposable |
| | | 79 | | { |
| | | 80 | | Task ExchangeDeclareAsync(string exchange, string type, bool durable, bool autoDelete, CancellationToken cancellatio |
| | | 81 | | Task QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object?>? ar |
| | | 82 | | Task QueueBindAsync(string queue, string exchange, string routingKey, CancellationToken cancellationToken = default) |
| | | 83 | | Task BasicQosAsync(ushort prefetchCount, CancellationToken cancellationToken = default); |
| | | 84 | | ValueTask BasicPublishAsync(string exchange, string routingKey, BasicProperties properties, ReadOnlyMemory<byte> bod |
| | | 85 | | Task<string> BasicConsumeAsync(string queue, Func<RabbitMqDelivery, Task> handler, CancellationToken cancellationTok |
| | | 86 | | Task BasicCancelAsync(string consumerTag, CancellationToken cancellationToken = default); |
| | | 87 | | ValueTask BasicAckAsync(ulong deliveryTag, CancellationToken cancellationToken = default); |
| | | 88 | | ValueTask BasicNackAsync(ulong deliveryTag, bool requeue, CancellationToken cancellationToken = default); |
| | | 89 | | Task CloseAsync(CancellationToken cancellationToken = default); |
| | | 90 | | } |
| | | 91 | | |
| | | 92 | | internal sealed class RabbitMqChannelAdapter(IChannel inner) : IRabbitMqChannel |
| | | 93 | | { |
| | | 94 | | /// <summary>Runs the ExchangeDeclareAsync operation.</summary> |
| | | 95 | | public Task ExchangeDeclareAsync(string exchange, string type, bool durable, bool autoDelete, CancellationToken canc |
| | | 96 | | => inner.ExchangeDeclareAsync(exchange, type, durable, autoDelete, cancellationToken: cancellationToken); |
| | | 97 | | |
| | | 98 | | /// <summary>Runs the QueueDeclareAsync operation.</summary> |
| | | 99 | | public async Task QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, |
| | | 100 | | => await inner.QueueDeclareAsync(queue, durable, exclusive, autoDelete, arguments, cancellationToken: cancellati |
| | | 101 | | |
| | | 102 | | /// <summary>Runs the QueueBindAsync operation.</summary> |
| | | 103 | | public Task QueueBindAsync(string queue, string exchange, string routingKey, CancellationToken cancellationToken = d |
| | | 104 | | => inner.QueueBindAsync(queue, exchange, routingKey, cancellationToken: cancellationToken); |
| | | 105 | | |
| | | 106 | | /// <summary>Runs the BasicQosAsync operation.</summary> |
| | | 107 | | public Task BasicQosAsync(ushort prefetchCount, CancellationToken cancellationToken = default) |
| | | 108 | | => inner.BasicQosAsync(0, prefetchCount, global: false, cancellationToken); |
| | | 109 | | |
| | | 110 | | /// <summary>Runs the BasicPublishAsync operation.</summary> |
| | | 111 | | public ValueTask BasicPublishAsync(string exchange, string routingKey, BasicProperties properties, ReadOnlyMemory<by |
| | | 112 | | => inner.BasicPublishAsync(exchange, routingKey, mandatory: true, properties, body, cancellationToken); |
| | | 113 | | |
| | | 114 | | /// <summary>Runs the BasicConsumeAsync operation.</summary> |
| | | 115 | | public Task<string> BasicConsumeAsync(string queue, Func<RabbitMqDelivery, Task> handler, CancellationToken cancella |
| | | 116 | | { |
| | | 117 | | var consumer = new AsyncEventingBasicConsumer(inner); |
| | | 118 | | consumer.ReceivedAsync += (_, args) => |
| | | 119 | | { |
| | | 120 | | var delivery = new RabbitMqDelivery( |
| | | 121 | | args.ConsumerTag, |
| | | 122 | | args.DeliveryTag, |
| | | 123 | | args.Redelivered, |
| | | 124 | | args.Exchange, |
| | | 125 | | args.RoutingKey, |
| | | 126 | | args.BasicProperties, |
| | | 127 | | args.Body, |
| | | 128 | | args.CancellationToken); |
| | | 129 | | return handler(delivery); |
| | | 130 | | }; |
| | | 131 | | |
| | | 132 | | return inner.BasicConsumeAsync( |
| | | 133 | | queue, |
| | | 134 | | autoAck: false, |
| | | 135 | | consumerTag: string.Empty, |
| | | 136 | | noLocal: false, |
| | | 137 | | exclusive: false, |
| | | 138 | | arguments: null, |
| | | 139 | | consumer, |
| | | 140 | | cancellationToken); |
| | | 141 | | } |
| | | 142 | | |
| | | 143 | | /// <summary>Runs the BasicCancelAsync operation.</summary> |
| | | 144 | | public Task BasicCancelAsync(string consumerTag, CancellationToken cancellationToken = default) |
| | | 145 | | => inner.BasicCancelAsync(consumerTag, cancellationToken: cancellationToken); |
| | | 146 | | |
| | | 147 | | /// <summary>Runs the BasicAckAsync operation.</summary> |
| | | 148 | | public ValueTask BasicAckAsync(ulong deliveryTag, CancellationToken cancellationToken = default) |
| | | 149 | | => inner.BasicAckAsync(deliveryTag, multiple: false, cancellationToken); |
| | | 150 | | |
| | | 151 | | /// <summary>Runs the BasicNackAsync operation.</summary> |
| | | 152 | | public ValueTask BasicNackAsync(ulong deliveryTag, bool requeue, CancellationToken cancellationToken = default) |
| | | 153 | | => inner.BasicNackAsync(deliveryTag, multiple: false, requeue, cancellationToken); |
| | | 154 | | |
| | | 155 | | /// <summary>Runs the CloseAsync operation.</summary> |
| | | 156 | | public Task CloseAsync(CancellationToken cancellationToken = default) |
| | | 157 | | => inner.CloseAsync(200, "AsyncResponse shutdown", abort: false, cancellationToken); |
| | | 158 | | |
| | | 159 | | /// <summary>Releases resources held by this instance.</summary> |
| | | 160 | | public ValueTask DisposeAsync() => inner.DisposeAsync(); |
| | | 161 | | } |
| | | 162 | | |
| | | 163 | | internal sealed record RabbitMqDelivery( |
| | | 164 | | string ConsumerTag, |
| | | 165 | | ulong DeliveryTag, |
| | | 166 | | bool Redelivered, |
| | | 167 | | string Exchange, |
| | | 168 | | string RoutingKey, |
| | | 169 | | IReadOnlyBasicProperties BasicProperties, |
| | | 170 | | ReadOnlyMemory<byte> Body, |
| | | 171 | | CancellationToken CancellationToken); |