/// <summary> /// Use RabbitMQ as Pub/Sub system. /// </summary> /// <param name="bootstrapper">Bootstrapper instance to configure</param> /// <returns>Configured bootstrapper instance</returns> public static Bootstrapper UseRabbitMQ( this Bootstrapper bootstrapper, RabbitConnectionInfos connectionInfos, RabbitNetworkInfos networkInfos, Action <RabbitSubscriberConfiguration> subscriberConfiguration = null, Action <RabbitPublisherConfiguration> publisherConfiguration = null) { var service = RabbitMQBootstrappService.Instance; var subscriberConf = new RabbitSubscriberConfiguration { ConnectionInfos = connectionInfos, NetworkInfos = networkInfos }; subscriberConfiguration?.Invoke(subscriberConf); service.BootstrappAction += (ctx) => { var publisherConf = new RabbitPublisherConfiguration() { ConnectionInfos = connectionInfos, NetworkInfos = networkInfos }; publisherConfiguration?.Invoke(publisherConf); bootstrapper.AddIoCRegistration(new InstanceTypeRegistration(subscriberConf, typeof(RabbitSubscriberConfiguration))); bootstrapper.AddIoCRegistration(new InstanceTypeRegistration(publisherConf, typeof(RabbitPublisherConfiguration))); bootstrapper.AddIoCRegistration(new TypeRegistration(typeof(RabbitPublisher), true)); bootstrapper.AddIoCRegistration(new TypeRegistration(typeof(RabbitSubscriber), true)); if (publisherConf.RoutingKeyFactory != null) { bootstrapper.AddIoCRegistration(new InstanceTypeRegistration(publisherConf.RoutingKeyFactory, typeof(IRoutingKeyFactory))); } }; bootstrapper.AddService(service); bootstrapper.OnPostBootstrapping += (c) => { ILoggerFactory loggerFactory = null; IScopeFactory scopeFactory = null; if (c.Scope != null) { loggerFactory = c.Scope.Resolve <ILoggerFactory>(); scopeFactory = c.Scope.Resolve <IScopeFactory>(); } if (loggerFactory == null) { loggerFactory = new LoggerFactory(); loggerFactory.AddProvider(new DebugLoggerProvider()); } RabbitMQBootstrappService.RabbitSubscriber = new RabbitSubscriber( loggerFactory, subscriberConf, scopeFactory); RabbitMQBootstrappService.RabbitSubscriber.Start(); }; return(bootstrapper); }
public async Task OneExchangePerService_NetworkConfiguration_AsExpected_Event() { try { bool eventReceived = false; var networkInfos = RabbitNetworkInfos.GetConfigurationFor("sub1", RabbitMQExchangeStrategy.ExchangePerService); networkInfos.DistantExchangeDescriptions.Add( new RabbitExchangeDescription(firstProducerEventExchangeName) ); var serviceQueue = networkInfos.ServiceQueueDescriptions[0]; serviceQueue.Bindings.Add(new RabbitQueueBindingDescription(firstProducerEventExchangeName)); var config = new RabbitSubscriberConfiguration { UseDeadLetterQueue = false, ConnectionInfos = GetConnectionInfos(), NetworkInfos = networkInfos, DispatchInMemory = false }; config.EventCustomCallback = (e) => eventReceived = e is RabbitEvent; var subscriber = new RabbitSubscriber( _loggerFactory, config, scopeFactory); subscriber.Start(); var enveloppeWithFirstEvent = GetEnveloppeDataForEvent(publisher: "pub1", content: "data"); _channel.BasicPublish( exchange: firstProducerEventExchangeName, routingKey: "", basicProperties: null, body: enveloppeWithFirstEvent); int awaitedTime = 0; while (awaitedTime <= 2000) { if (eventReceived) { break; } await Task.Delay(10); awaitedTime += 10; } eventReceived.Should().BeTrue(); } finally { DeleteData(); } }
public async Task RabbitMQSubscriber_Should_Consider_AckStrategy_Ack_On_Receive_Fail_Should_Remove_MessageFromQueue_CallbackExc() { try { var networkInfos = RabbitNetworkInfos.GetConfigurationFor("sub1", RabbitMQExchangeStrategy.SingleExchange); var config = new RabbitSubscriberConfiguration { UseDeadLetterQueue = true, ConnectionInfos = GetConnectionInfos(), NetworkInfos = networkInfos, AckStrategy = AckStrategy.AckOnReceive }; config.EventCustomCallback += (_) => throw new InvalidOperationException(); var subscriber = new RabbitSubscriber( _loggerFactory, config); subscriber.Start(); var evt = new ExceptionEvent(); _channel.BasicPublish( Consts.CONST_CQE_EXCHANGE_NAME, "", body: Encoding.UTF8.GetBytes( JsonConvert.SerializeObject( new Enveloppe( JsonConvert.SerializeObject(evt), typeof(ExceptionEvent), publisher1Name)))); await Task.Delay(250); int awaitedTime = 0; BasicGetResult result = null; while (awaitedTime <= 750) { result = _channel.BasicGet(Consts.CONST_DEAD_LETTER_QUEUE_NAME, true); if (result != null) { break; } await Task.Delay(10); awaitedTime += 10; } result.Should().BeNull(); } finally { DeleteData(); } }
public async Task RabbitSubscriber_Should_Consider_AckStrategy_Ack_On_Success_CallbackExc() { try { bool eventReceived = false; var messages = new List <object>(); var networkInfos = RabbitNetworkInfos.GetConfigurationFor("sub1", RabbitMQExchangeStrategy.SingleExchange); var config = new RabbitSubscriberConfiguration { UseDeadLetterQueue = true, ConnectionInfos = GetConnectionInfos(), NetworkInfos = networkInfos, DispatchInMemory = false }; config.EventCustomCallback = (e) => { messages.Add(e); eventReceived = true; }; var subscriber = new RabbitSubscriber( _loggerFactory, config); subscriber.Start(); var evt = new AutoAckEvent(); _channel.BasicPublish( Consts.CONST_CQE_EXCHANGE_NAME, "", body: Encoding.UTF8.GetBytes( JsonConvert.SerializeObject( new Enveloppe( JsonConvert.SerializeObject(evt), typeof(AutoAckEvent), publisher1Name)))); int awaitedTime = 0; while (awaitedTime <= 2000) { if (eventReceived) { break; } await Task.Delay(10); awaitedTime += 10; } eventReceived.Should().BeTrue(); var result = _channel.BasicGet(Consts.CONST_DEAD_LETTER_QUEUE_NAME, true); result.Should().BeNull(); } finally { DeleteData(); } }
public async Task Command_Should_Be_Send_AsDirect() { try { bool commandReceived = false; var networkInfos = RabbitNetworkInfos.GetConfigurationFor("sub1", RabbitMQExchangeStrategy.SingleExchange); var serviceQueue = networkInfos.ServiceQueueDescriptions[0]; var config = new RabbitSubscriberConfiguration { UseDeadLetterQueue = false, ConnectionInfos = GetConnectionInfos(), NetworkInfos = networkInfos, DispatchInMemory = false }; config.CommandCustomCallback = (c) => commandReceived = c is RabbitCommand; var subscriber = new RabbitSubscriber( _loggerFactory, config, scopeFactory); subscriber.Start(); var enveloppeWithCommand = GetEnveloppeDataForCommand(publisher: "pub1", content: "data"); _channel.BasicPublish( exchange: "", routingKey: serviceQueue.QueueName, basicProperties: null, body: enveloppeWithCommand); int awaitedTime = 0; while (awaitedTime <= 2000) { if (commandReceived) { break; } await Task.Delay(10); awaitedTime += 10; } commandReceived.Should().BeTrue(); } finally { DeleteData(); } }
public async Task OneExchange_Network_Configuration_AsExpected_Event() { try { bool eventReceived = false; var networkInfos = RabbitNetworkInfos.GetConfigurationFor("sub1", RabbitMQExchangeStrategy.SingleExchange); var config = new RabbitSubscriberConfiguration { UseDeadLetterQueue = false, ConnectionInfos = GetConnectionInfos(), NetworkInfos = networkInfos, DispatchInMemory = false }; config.EventCustomCallback = (e) => eventReceived = e is RabbitEvent; var subscriber = new RabbitSubscriber( _loggerFactory, config, scopeFactory); subscriber.Start(); var enveloppeWithFirstEvent = GetEnveloppeDataForEvent(publisher: "pub1", content: "data"); _channel.BasicPublish( exchange: Consts.CONST_CQE_EXCHANGE_NAME, routingKey: "", basicProperties: null, body: enveloppeWithFirstEvent); int awaitedTime = 0; while (awaitedTime <= 2000) { if (eventReceived) { break; } await Task.Delay(10); awaitedTime += 10; } eventReceived.Should().BeTrue(); } finally { DeleteData(); } }
public static void PublishMessageTest() { IRabbitPublisherConfiguration publishConfig = new RabbitPublisherConfiguration(); RabbitPublisher <CommandArgs> publisher = new RabbitPublisher <CommandArgs>(publishConfig); IRabbitSubscriberConfiguration subscriberConfig = new RabbitSubscriberConfiguration(); SandboxConsumer consumer = new SandboxConsumer(); RabbitSubscriber <SandboxConsumer> subscriber = new RabbitSubscriber <SandboxConsumer>(consumer, subscriberConfig); subscriber.Start(); publisher.Publish(new CommandArgs { Value = "some data" }); Console.ReadKey(); }
public static void DeclareExchangesAndQueueForSubscriber( IModel channel, RabbitSubscriberConfiguration config) { if (config.UseDeadLetterQueue) { channel.ExchangeDeclare(Consts.CONST_DEAD_LETTER_EXCHANGE_NAME, "fanout", true, false, null); channel.QueueDeclare(Consts.CONST_DEAD_LETTER_QUEUE_NAME, true, false, false, null); channel.QueueBind(Consts.CONST_DEAD_LETTER_QUEUE_NAME, Consts.CONST_DEAD_LETTER_EXCHANGE_NAME, "", null); } DeclareExchanges(channel, config.NetworkInfos.ServiceExchangeDescriptions.Concat(config.NetworkInfos.DistantExchangeDescriptions)); foreach (var queueDescription in config.NetworkInfos.ServiceQueueDescriptions) { if (config.UseDeadLetterQueue && !queueDescription.AdditionnalProperties.ContainsKey(Consts.CONST_DEAD_LETTER_EXCHANGE_RABBIT_KEY)) { queueDescription.AdditionnalProperties.Add(Consts.CONST_DEAD_LETTER_EXCHANGE_RABBIT_KEY, Consts.CONST_DEAD_LETTER_EXCHANGE_NAME); } DeclareQueue(channel, queueDescription); } }
public void Test1() { var option = new RabbitSubscriberConfiguration { ExchangeName = "ex", QueueName = "ex.q", ConsumerTag = "ct", ExchangeType = "Fanout" }; var message = "{\"message\": \"no h\"}"; var body = Encoding.UTF8.GetBytes(message); sut.StartProcess <SampleMessage>(option, (payload) => { Console.WriteLine("model {0}", payload); Assert.Equal("no h", payload.Message); return(Task.CompletedTask); }, null); channel.BasicPublish(option.ExchangeName, "", body: body); }
public void Test2() { var option = new RabbitSubscriberConfiguration { ExchangeName = "ex", QueueName = "ex.q", ConsumerTag = "ct", ExchangeType = "Fanout" }; var waitHandle = new AutoResetEvent(false); var message = "{\"message\": \"no h\"}"; var body = Encoding.UTF8.GetBytes(message); sut.StartProcess <SampleMessage>(option, (payload) => { if (payload.Message == "no h") { throw new Exception("error"); } return(Task.CompletedTask); }, payload => { Assert.Equal("no h", payload.Message.Message); waitHandle.Set(); return(Task.CompletedTask); }); channel.BasicPublish(option.ExchangeName, "", body: body); if (!waitHandle.WaitOne(1000, false)) { Assert.False(true, "Timeout"); } }
public Task StartProcess <T>( RabbitSubscriberConfiguration option, Func <T, Task> queueProcessor, Func <DlxMessage <T>, Task> dlxProcessor) where T : class { var(queueName, exchangeName, consumerTag, maxRetry, backoffRateInSeconds, exchangeType, routingKey) = (option.QueueName, option.ExchangeName, option.ConsumerTag, option.MaxRetry, option.BackoffRateInSeconds, option.ExchangeType, option.RoutingKey); if (maxRetry.HasValue && !backoffRateInSeconds.HasValue) { throw new ArgumentException("Retry count should be accompanied by back off rate"); } if (exchangeType.ToLower() != "fanout" && string.IsNullOrWhiteSpace(routingKey)) { throw new ArgumentException("non-fanout exchange type must have routing key"); } connection = rabbitConnFactory.CreateConnection(); var channel = connection.CreateModel(); channelsToDispose.Add(channel); CreateDlx(dlxProcessor, channel, exchangeName, queueName, consumerTag); channel.ExchangeDeclare(exchange: exchangeName, type: exchangeType.ToLower()); var qName = channel.QueueDeclare(queueName, true, false, false, null).QueueName; channel.QueueBind(qName, exchangeName, routingKey ?? "", null); var consumer = new AsyncEventingBasicConsumer(channel); consumer.Received += async(model, ea) => { T deserializedObject = default; try { var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); if (message is null) { logger.LogInformation("Null message payload, not processing message"); channel.BasicAck(ea.DeliveryTag, false); return; } deserializedObject = JsonSerializer.Deserialize <T>(message, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); if (deserializedObject is null) { logger.LogInformation("Deserialised message resulted in null object, not processing message"); return; } await queueProcessor(deserializedObject); channel.BasicAck(ea.DeliveryTag, false); } catch (Exception e) { // Get routing keys from (first (latest)) headers to determine the current retry backoff var routingKeysInMillis = ea.BasicProperties?.Headers != null ? ((ea.BasicProperties.Headers["x-death"] as List <object>).FirstOrDefault() as Dictionary <string, object>)?["routing-keys"] : null; try { if (!maxRetry.HasValue || maxRetry <= 0) { // No retry, just dead letter directly SendToDlx(channel, exchangeName, deserializedObject, e, ea); return; } // try convert routingkeys backoff and convert to retry count below if (routingKeysInMillis != null && UInt32.TryParse(Encoding.UTF8.GetString((routingKeysInMillis as List <object>)[0] as byte[]), out uint retryCount)) { retryCount = (retryCount / (backoffRateInSeconds.Value * 1000)) + 1; // when it's in here, means it has gone through first round if (retryCount <= maxRetry) { SendToRetryExchange(channel, retryCount, backoffRateInSeconds.Value, exchangeName, queueName, routingKey, ea); return; } SendToDlx(channel, exchangeName, deserializedObject, e, ea); return; } SendToRetryExchange(channel, 1, backoffRateInSeconds.Value, exchangeName, queueName, routingKey, ea); } catch (Exception ex) { logger.LogError(ex, "Error: {0}", ex.Message); } } }; channel.BasicConsume( queue: qName, autoAck: false, consumerTag: $"{consumerTag ?? Assembly.GetExecutingAssembly().ToString()} - queue", consumer: consumer); return(Task.CompletedTask); }