using System; using System.Collections.Generic; using System.Threading.Tasks; using RabbitMQ.Client; using RabbitMQ.Client.Exceptions; using RabbitMQ.Client.Framing; using Tapeti.Config; using Tapeti.Tasks; namespace Tapeti.Connection { public class TapetiWorker { public TapetiConnectionParams ConnectionParams { get; set; } public string Exchange { get; set; } private readonly IDependencyResolver dependencyResolver; private readonly IReadOnlyList messageMiddleware; private readonly IMessageSerializer messageSerializer; private readonly IRoutingKeyStrategy routingKeyStrategy; private readonly Lazy taskQueue = new Lazy(); private RabbitMQ.Client.IConnection connection; private IModel channelInstance; public TapetiWorker(IDependencyResolver dependencyResolver, IReadOnlyList messageMiddleware) { this.dependencyResolver = dependencyResolver; this.messageMiddleware = messageMiddleware; messageSerializer = dependencyResolver.Resolve(); routingKeyStrategy = dependencyResolver.Resolve(); } public Task Publish(object message, IBasicProperties properties) { return taskQueue.Value.Add(async () => { var messageProperties = properties ?? new BasicProperties(); if (messageProperties.Timestamp.UnixTime == 0) messageProperties.Timestamp = new AmqpTimestamp(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()); var body = messageSerializer.Serialize(message, messageProperties); (await GetChannel()) .BasicPublish(Exchange, routingKeyStrategy.GetRoutingKey(message.GetType()), false, messageProperties, body); }).Unwrap(); } public Task Consume(string queueName, IEnumerable bindings) { return taskQueue.Value.Add(async () => { (await GetChannel()).BasicConsume(queueName, false, new TapetiConsumer(this, dependencyResolver, bindings, messageMiddleware)); }).Unwrap(); } public async Task Subscribe(IQueue queue) { var queueName = await taskQueue.Value.Add(async () => { var channel = await GetChannel(); if (queue.Dynamic) { var dynamicQueue = channel.QueueDeclare(); foreach (var binding in queue.Bindings) { var routingKey = routingKeyStrategy.GetRoutingKey(binding.MessageClass); channel.QueueBind(dynamicQueue.QueueName, Exchange, routingKey); } return dynamicQueue.QueueName; } channel.QueueDeclarePassive(queue.Name); return queue.Name; }).Unwrap(); await Consume(queueName, queue.Bindings); } public Task Respond(ulong deliveryTag, ConsumeResponse response) { return taskQueue.Value.Add(async () => { switch (response) { case ConsumeResponse.Ack: (await GetChannel()).BasicAck(deliveryTag, false); break; case ConsumeResponse.Nack: (await GetChannel()).BasicNack(deliveryTag, false, false); break; case ConsumeResponse.Requeue: (await GetChannel()).BasicNack(deliveryTag, false, true); break; } }).Unwrap(); } public Task Close() { if (!taskQueue.IsValueCreated) return Task.CompletedTask; return taskQueue.Value.Add(() => { if (channelInstance != null) { channelInstance.Dispose(); channelInstance = null; } // ReSharper disable once InvertIf if (connection != null) { connection.Dispose(); connection = null; } taskQueue.Value.Dispose(); }); } /// /// Only call this from a task in the taskQueue to ensure IModel is only used /// by a single thread, as is recommended in the RabbitMQ .NET Client documentation. /// private async Task GetChannel() { if (channelInstance != null) return channelInstance; var connectionFactory = new ConnectionFactory { HostName = ConnectionParams.HostName, Port = ConnectionParams.Port, VirtualHost = ConnectionParams.VirtualHost, UserName = ConnectionParams.Username, Password = ConnectionParams.Password, AutomaticRecoveryEnabled = true, RequestedHeartbeat = 30 }; while (true) { try { connection = connectionFactory.CreateConnection(); channelInstance = connection.CreateModel(); break; } catch (BrokerUnreachableException) { await Task.Delay(5000); } } return channelInstance; } } }