private void RedeclareExchangesAndQueues(AmqpModelContainer model, string serviceName) { //Redeclare exchanges and queues every minute if exchanges and queues are transient, or the first time client is sending a message TimeSpan elapsedSinceLastDeclareExchange = TimeSpan.FromMilliseconds(Environment.TickCount - lastExchangeDeclareTickCount); //TODO: Partition elapsedSinceLastDeclareExchange by serviceName and connection so that redeclares take place on new servicenames and connections. //Discovering firstDeclare by comparing lastExchangeDeclareTickCount to zero is not perfect //because tickcount can wrap back to zero (through the negative number range), if client is running long enough. //However, redeclaring exchanges and queues are a safe operation, so this is okay if it occurs more than once in persistent queues. bool firstDeclare = lastExchangeDeclareTickCount == 0; if (firstDeclare || (!messagingConfig.PersistentWorkQueuesAndExchanges && (elapsedSinceLastDeclareExchange.TotalMilliseconds < 0 || elapsedSinceLastDeclareExchange.TotalSeconds > 60))) { if (!firstDeclare) { //All threads must attempt to declare exchanges and queues if it hasn't been previously declared //(for instance, all threads were started at once) //So do not swap out this value on first declare lastExchangeDeclareTickCount = Environment.TickCount; } AmqpUtils.DeclareExchangeAndQueues(model.Channel, messageMapper, messagingConfig, serviceName, exchangeDeclareSync, null); if (firstDeclare) { //Swap out this value after declaring on firstdeclare lastExchangeDeclareTickCount = Environment.TickCount; } } }
/// <summary> /// Initislizes a new instance of the <see cref="RestBusSubscriber"/> /// </summary> /// <param name="messageMapper">The <see cref="IMessageMapper"/> used by the subscriber.</param> /// <param name="settings">The subscriber settings</param> public RestBusSubscriber(IMessageMapper messageMapper, SubscriberSettings settings) { this.messageMapper = messageMapper; messagingConfig = messageMapper.MessagingConfig; //Fetched only once if (messagingConfig == null) { throw new ArgumentException("messageMapper.MessagingConfig returned null", "messageMapper"); } if (messageMapper.SupportedExchangeKinds == default(ExchangeKind)) { throw new ArgumentException("messageMapper.SupportedExchangeKinds is not set up.", "messageMapper"); } serviceName = (messageMapper.GetServiceName(null) ?? String.Empty).Trim(); subscriberIdHeader = new string[] { AmqpUtils.GetNewExclusiveQueueId() }; this.connectionFactory = new ConnectionFactory(); connectionFactory.Uri = new Uri(messageMapper.ServerUris[0].Uri); ConnectionNames = messageMapper.ServerUris.Select(u => u.FriendlyName ?? String.Empty).ToArray(); connectionFactory.RequestedHeartbeat = Client.RPCStrategyHelpers.HEART_BEAT; this.Settings = settings ?? new SubscriberSettings(); //Make sure a default value is set, if not supplied by user. this.Settings.Subscriber = this; //Indicate that the subcriber settings is owned by this subscriber. }
public void TestAmqpFixtureRequestReply() { var amqpFixture = new AmqpFixture(); //Setup Listener: bool MessageReceiver(Message message) { var subject = message.Properties?.Subject; if (subject != null && subject == "EchoMessage") { var m = AmqpUtils.DeserializeMessage(message.Body); amqpFixture.AmqpService.SendMessage(message.Properties.ReplyTo, message.Properties.Subject, m, message.Properties.CorrelationId); return(true); } return(false); } var listener = amqpFixture.AmqpService.GetAmqpListener(MessageReceiver, "TestRequest"); //RequestReply Message: var requestMsg = new { Key = "Test" }; var response = amqpFixture.AmqpService.RequestReply(requestMsg, "EchoMessage", "TestRequest"); Assert.NotNull(response); Assert.NotNull(response["Key"]); Assert.Equal("Test", response["Key"]); listener.Disconnect(); }
private bool OnMessage(Message message) { var messageBodyObj = AmqpUtils.DeserializeMessage(message.Body); if (messageBodyObj == null) { throw new Exception("Could not deserialize message"); } try { switch (message.Properties.Subject) { case "PutResource": { var data = messageBodyObj["body"].ToObject <string>(); var hashCode = Hash64Str(data); _amqpService.SendMessage(message.Properties.ReplyTo, message.Properties.Subject, new { responseObject = new { hash = hashCode, uuid = Guid.NewGuid() } }, message.Properties.CorrelationId); break; } default: throw new NotImplementedException(); } } catch (Exception e) { throw; } return(true); }
public RestBusSubscriber(IMessageMapper messageMapper) { exchangeInfo = messageMapper.GetExchangeInfo(); subscriberId = AmqpUtils.GetRandomId(); this.connectionFactory = new ConnectionFactory(); connectionFactory.Uri = exchangeInfo.ServerAddress; connectionFactory.RequestedHeartbeat = Client.RestBusClient.HEART_BEAT; }
// // // public static string PrintAmqpJsonMessage(this Message message) // { // var sb = new StringBuilder(); // sb.Append("----------------------------------------------------------------------------\n"); // sb.Append("Body........................: \n"); // sb.Append(jsonText); // sb.Append("\n"); // sb.Append("----------------------------------------------------------------------------\n"); // // return sb.ToString(); // } public static void DebugLogAmqpMessage(this ILog logger, string message, Amqp.Message m, string queue = null) { try { var msg = AmqpUtils.DeserializeMessage(m.Body); var formattedJson = JsonConvert.SerializeObject(msg, DebugPrintSerializerSettings); var sb = new StringBuilder(message).Append("\n"); sb.Append("----------------------------------------------------------------------------\n"); if (queue != null) { sb.Append(PadRight("Queue")).Append(queue).Append("\n"); } if (m.Properties != null) { if (m.Properties.ReplyTo != null) { sb.Append(PadRight("ReplyTo")).Append(m.Properties.ReplyTo).Append("\n"); } if (m.Properties.Subject != null) { sb.Append(PadRight("Subject")).Append(m.Properties.Subject).Append("\n"); } if (m.Properties.CorrelationId != null) { sb.Append(PadRight("CorrelationId")).Append(m.Properties.CorrelationId).Append("\n"); } if (m.Properties.MessageId != null) { sb.Append(PadRight("Properties.MessageId")).Append(m.Properties.MessageId).Append("\n"); } } if (m.ApplicationProperties != null) { foreach (var pair in m.ApplicationProperties.Map) { sb.Append(PadRight($"Application.{pair.Key}")).Append(pair.Value); } } sb.Append(PadRight("Body")).Append(formattedJson).Append("\n"); sb.Append("----------------------------------------------------------------------------\n"); logger.Debug(sb.ToString()); } catch (Exception e) { logger.Warn("Error in CusomLoggerExtensions while logging Amqp.Message", e); } }
private static void DeclareIndirectReplyToQueue(IModel channel, string queueName) { //The queue is set to be auto deleted once the last consumer stops using it. //However, RabbitMQ will not delete the queue if no consumer ever got to use it. //Passing x-expires in solves that: It tells RabbitMQ to delete the queue, if no one uses it within the specified time. var callbackQueueArgs = new Dictionary <string, object>(); callbackQueueArgs.Add("x-expires", (long)AmqpUtils.GetCallbackQueueExpiry().TotalMilliseconds); //Declare call back queue channel.QueueDeclare(queueName, false, false, true, callbackQueueArgs); }
public CallbackQueueRPCStrategy(ClientSettings clientSettings, string queueName) { this.clientSettings = clientSettings; if (String.IsNullOrEmpty(queueName)) { queueName = SynchronizedRandom.Instance.Next().ToString(); } else { queueName = queueName.Trim(); } this.indirectReplyToQueueName = AmqpUtils.GetCallbackQueueName(queueName, AmqpUtils.GetNewExclusiveQueueId()); //Initialize expectedResponses expectedResponses = new ConcurrentDictionary <string, ExpectedResponse>(); }
public void TestAmqpFixtureSendAndReceive() { var amqpFixture = new AmqpFixture(); var service = amqpFixture.AmqpService; var num = new Random((int)DateTime.Now.Ticks).Next(999999); bool MessageReceiver(Message message) { var body = AmqpUtils.DeserializeMessage(message.Body); Assert.Equal("Test", body["Name"]); Assert.Equal(true, body["Test"]); ReceivedMessageNum++; return(true); } var amqpListener = service.GetAmqpListener(MessageReceiver, $"TestQueue/{num}"); Task.Delay(100); const int msgNum = 5; for (var i = 0; i < msgNum; i++) { var msg = new { Name = "Test", Test = true, Value = i }; service.SendMessage($"TestQueue/{num}", "Test", msg); } var watch = Stopwatch.StartNew(); while (ReceivedMessageNum < msgNum && watch.ElapsedMilliseconds < 10000) { Thread.Sleep(100); } Assert.Equal(msgNum, ReceivedMessageNum); amqpListener.Disconnect(); }
/// <summary>Send an HTTP request as an asynchronous operation.</summary> /// <returns>Returns <see cref="T:System.Threading.Tasks.Task`1" />.The task object representing the asynchronous operation.</returns> /// <param name="request">The HTTP request message to send.</param> /// <param name="cancellationToken">The cancellation token to cancel operation.</param> /// <exception cref="T:System.ArgumentNullException">The <paramref name="request" /> was null.</exception> public override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException("request"); } if (request.RequestUri == null && BaseAddress == null) { throw new InvalidOperationException("The request URI must either be set or BaseAddress must be set"); } if (disposed) { throw new ObjectDisposedException(GetType().FullName); } hasKickStarted = true; PrepareMessage(request); //Get Request Options RequestOptions requestOptions = GetRequestOptions(request); var messageProperties = GetMessagingProperties(requestOptions); //Determine if message expects a response TimeSpan requestTimeout = GetRequestTimeout(requestOptions); //TODO: expectingResponse has a slightly different meaning in publisher confirms //where timespan may be longer than zero but MessageExpectsReply is false //in which case the timeout only applies to how long to wait for the publisher confirmation. bool expectingResponse = requestTimeout != TimeSpan.Zero && GetExpectsReply(request); //Declare messaging resources ExpectedResponse arrival = null; AmqpModelContainer model = null; bool modelClosed = false; string correlationId = null; //Get channel pool and decide on RPC strategy var pool = connectionMgr.GetConnectedPool(); IRPCStrategy rpcStrategy = pool.IsDirectReplyToCapable && !Settings.DisableDirectReplies ? directStrategy : callbackStrategy; try { #region Ensure CallbackQueue is started (If Using CallbackQueue Strategy) rpcStrategy.StartStrategy(pool, expectingResponse); #endregion #region Populate BasicProperties //Fill BasicProperties BasicProperties basicProperties = new BasicProperties(); //Set message delivery mode -- Make message persistent if either: // 1. Properties.Persistent is true // 2. messagingConfig.PersistentMessages is true and Properties.Persistent is null // 3. messagingConfig.PersistentMessages is true and Properties.Persistent is true if (messageProperties.Persistent == true || (messagingConfig.PersistentMessages && messageProperties.Persistent != false)) { basicProperties.Persistent = true; } //Set Exchange Headers var exchangeHeaders = messageProperties.Headers ?? messageMapper.GetHeaders(request); if (exchangeHeaders != null) { basicProperties.Headers = exchangeHeaders; } if (expectingResponse) { //Set CorrelationId correlationId = correlationIdGen.GetNextId(); basicProperties.CorrelationId = correlationId; //Set Expiration if messageProperties doesn't override Client.Timeout, RequestOptions and MessageMapper. if (!messageProperties.Expiration.HasValue && requestTimeout != System.Threading.Timeout.InfiniteTimeSpan && (messagingConfig.MessageExpires == null || messagingConfig.MessageExpires(request))) { if (requestTimeout.TotalMilliseconds > Int32.MaxValue) { basicProperties.Expiration = Int32.MaxValue.ToString(); } else { basicProperties.Expiration = ((int)requestTimeout.TotalMilliseconds).ToString(); } } } else if (!messageProperties.Expiration.HasValue && (messagingConfig.MessageExpires == null || messagingConfig.MessageExpires(request))) { //Request has a zero timeout and the message mapper indicates it should expire and messageproperties expiration is not set: //Set the expiration to zero which means RabbitMQ will only transmit if there is a consumer ready to receive it. //If there is no ready consumer, RabbitMQ drops the message. See https://www.rabbitmq.com/ttl.html basicProperties.Expiration = "0"; } //Set expiration if set in message properties if (messageProperties.Expiration.HasValue) { if (messageProperties.Expiration != System.Threading.Timeout.InfiniteTimeSpan) { var expiration = messageProperties.Expiration.Value.Duration(); if (expiration.TotalMilliseconds > Int32.MaxValue) { basicProperties.Expiration = Int32.MaxValue.ToString(); } else { basicProperties.Expiration = ((int)expiration.TotalMilliseconds).ToString(); } } else { //Infinite Timespan indicates that message should never expire basicProperties.ClearExpiration(); } } #endregion #region Get Ready to Send Message model = rpcStrategy.GetModel(pool, false); var serviceName = (requestOptions == null || requestOptions.ServiceName == null) ? (messageMapper.GetServiceName(request) ?? String.Empty).Trim() : requestOptions.ServiceName.Trim(); RedeclareExchangesAndQueues(model, serviceName); //TODO: Check if cancellation token was set before operation even began var taskSource = new TaskCompletionSource <HttpResponseMessage>(); var exchangeKind = ExchangeKind.Direct; //TODO: Get ExchangeKind from CLient.Settings.ExchangeKind //TODO: Pull exchangeName from a concurrent dictionary that has a key of serviceName, exchangeKind //exchangeKind could be an index into arrays that have concurrentDictionaries. var exchangeName = AmqpUtils.GetExchangeName(messagingConfig, serviceName, exchangeKind); #endregion #region Start waiting for response //Start waiting for response if a request timeout is set. if (expectingResponse) { //TODO: Better to just check if cancellationHasbeen requested instead of checking if it's None if (!cancellationToken.Equals(System.Threading.CancellationToken.None)) { //TODO: Have cancellationtokens cancel event trigger callbackHandle //In fact turn this whole thing into an extension } arrival = rpcStrategy.PrepareForResponse(correlationId, basicProperties, model, request, requestTimeout, cancellationToken, taskSource); } #endregion #region Send Message //TODO: Implement routing to a different exchangeKind via substituting exchangeName //Send message model.Channel.BasicPublish(exchangeName, messageProperties.RoutingKey ?? messageMapper.GetRoutingKey(request, exchangeKind) ?? AmqpUtils.GetWorkQueueRoutingKey(), basicProperties, request.ToHttpRequestPacket().Serialize()); //Close channel if (!expectingResponse || rpcStrategy.ReturnModelAfterSending) { CloseAmqpModel(model); modelClosed = true; } #endregion #region Cleanup if not expecting response //Exit with OK result if no request timeout was set. if (!expectingResponse) { //TODO: Investigate adding a publisher confirm for zero timeout messages so we know that RabbitMQ did pick up the message before replying OK. //Might add extra complexity to this class. //Zero timespan means the client isn't interested in a response taskSource.SetResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = _emptyByteArrayContent }); rpcStrategy.CleanupMessagingResources(correlationId, arrival); } #endregion return(taskSource.Task); } catch (Exception ex) { //TODO: Log this if (model != null && !modelClosed) { if (expectingResponse && model.Flags == ChannelFlags.RPC || model.Flags == ChannelFlags.RPCWithPublisherConfirms) { //Model might still be in use in waiting thread and so unsafe to be recycled model.Discard = true; } CloseAmqpModel(model); } rpcStrategy.CleanupMessagingResources(correlationId, arrival); if (ex is HttpRequestException) { throw; } else { throw GetWrappedException("An error occurred while sending the request.", ex); } } }
public void Restart() { hasStarted.Set(true); //CLose connections and channels if (subscriberChannel != null) { if (subscriberConsumer != null) { try { subscriberChannel.Channel.BasicCancel(subscriberConsumer.ConsumerTag); } catch { } } try { subscriberChannel.Close(); } catch { } } if (workChannel != null) { if (workConsumer != null) { try { workChannel.Channel.BasicCancel(workConsumer.ConsumerTag); } catch { } } try { workChannel.Close(); } catch { } } if (_subscriberPool != null) { _subscriberPool.Dispose(); } //NOTE: CreateConnection() can throw BrokerUnreachableException //That's okay because the exception needs to propagate to Reconnect() or Start() var conn = connectionFactory.CreateConnection(); if (connectionBroken != null) { connectionBroken.Dispose(); } connectionBroken = new CancellationTokenSource(); if (stopWaitingOnQueue != null) { stopWaitingOnQueue.Dispose(); } stopWaitingOnQueue = CancellationTokenSource.CreateLinkedTokenSource(disposedCancellationSource.Token, connectionBroken.Token); var pool = new AmqpChannelPooler(conn); _subscriberPool = pool; //Use pool reference henceforth. //Create work channel and declare exchanges and queues workChannel = pool.GetModel(ChannelFlags.Consumer); //Redeclare exchanges and queues AmqpUtils.DeclareExchangeAndQueues(workChannel.Channel, messageMapper, messagingConfig, serviceName, exchangeDeclareSync, Id); //Listen on work queue workConsumer = new ConcurrentQueueingConsumer(workChannel.Channel, requestQueued); string workQueueName = AmqpUtils.GetWorkQueueName(messagingConfig, serviceName); workChannel.Channel.BasicQos(0, (ushort)Settings.PrefetchCount, false); workChannel.Channel.BasicConsume(workQueueName, Settings.AckBehavior == SubscriberAckBehavior.Automatic, workConsumer); //Listen on subscriber queue subscriberChannel = pool.GetModel(ChannelFlags.Consumer); subscriberConsumer = new ConcurrentQueueingConsumer(subscriberChannel.Channel, requestQueued); string subscriberWorkQueueName = AmqpUtils.GetSubscriberQueueName(serviceName, Id); subscriberChannel.Channel.BasicQos(0, (ushort)Settings.PrefetchCount, false); subscriberChannel.Channel.BasicConsume(subscriberWorkQueueName, Settings.AckBehavior == SubscriberAckBehavior.Automatic, subscriberConsumer); //Cancel connectionBroken on connection/consumer problems pool.Connection.ConnectionShutdown += (s, e) => { connectionBroken.Cancel(); }; workConsumer.ConsumerCancelled += (s, e) => { connectionBroken.Cancel(); }; subscriberConsumer.ConsumerCancelled += (s, e) => { connectionBroken.Cancel(); }; }
public void TestInteropClient() { var requestQueueName = "Resources"; // Connection.DisableServerCertValidation = true; // uncomment the following to write frame traces // Amqp.Trace.TraceLevel = TraceLevel.Frame; // Amqp.Trace.TraceListener = (l, f, a) => // { // switch (l) // { // case TraceLevel.Error: Logger.ErrorFormat(f, a); break; // case TraceLevel.Warning: Logger.WarnFormat(f, a); break; // case TraceLevel.Information: // case TraceLevel.Output: Logger.InfoFormat(f, a); break; // case TraceLevel.Verbose: // case TraceLevel.Frame: // default: Logger.DebugFormat(f, a); break; // } // }; int sizeKB = 1024 * 10; //10MB //generate large payload var rand = new Random(System.DateTimeOffset.Now.Millisecond); var ms = new MemoryStream(); var buffer = new byte[1024]; for (var written = 0L; written < sizeKB; written++) { rand.NextBytes(buffer); ms.Write(buffer, 0, buffer.Length); } var base64String = Convert.ToBase64String(ms.ToArray()); var msg = new { name = $"rand{sizeKB}KB.bin", mimeType = "application/octet-stream", body = base64String }; var jsonText = JsonConvert.SerializeObject(msg); var bytes = Encoding.UTF8.GetBytes(jsonText); var amqpService = Fixture.AmqpService; // try // { var session = amqpService.GetSession(); // Sender attaches to fixed request queue name var sender = new SenderLink(session, "Interop.Client-sender", requestQueueName); // Receiver attaches to dynamic address. // Discover its name when it attaches. var replyTo = ""; var receiverAttached = new ManualResetEvent(false); void OnReceiverAttached(ILink l, Attach a) { replyTo = ((Source)a.Source).Address; receiverAttached.Set(); } // Create receiver and wait for it to attach. var receiver = new ReceiverLink( session, "Interop.Client-receiver", new Source() { Dynamic = true }, OnReceiverAttached); if (receiverAttached.WaitOne(10000)) { // Receiver is attached. Logger.Debug("Receiver is attached"); var request = new Message(bytes) { Properties = new Properties() { MessageId = "request" + 0, ReplyTo = replyTo, Subject = "PutResource" } }; sender.Send(request); var response = receiver.Receive(); if (null != response) { receiver.Accept(response); var message = AmqpUtils.DeserializeMessage(response?.Body); var uuid = message["responseObject"]?["uuid"]?.ToString(); Assert.NotNull(uuid); var guid = Guid.Parse(uuid); } else { throw new ApplicationException( string.Format("Receiver timeout receiving response {0}", 0)); } } else { throw new ApplicationException("Receiver attach timeout"); } receiver.Close(); sender.Close(); session.Close(); return; // } // catch (Exception e) // { // Console.Error.WriteLine("Exception {0}.", e); // if (null != connection) // { // connection.Close(); // } // } }
public JObject SendInteropClientRequest(object msg, string subject, string requestQueue) { JObject message = null; var bytes = AmqpUtils.SerializeMessage(msg); var amqpService = Fixture.AmqpService; var session = amqpService.GetSession(); SenderLink sender = null; ReceiverLink receiver = null; try { // Sender attaches to fixed request queue name sender = new SenderLink(session, "Interop.Client-sender", requestQueue); // Receiver attaches to dynamic address. // Discover its name when it attaches. var replyTo = ""; var receiverAttached = new ManualResetEvent(false); void OnReceiverAttached(ILink l, Attach a) { replyTo = ((Source)a.Source).Address; receiverAttached.Set(); } // Create receiver and wait for it to attach. receiver = new ReceiverLink( session, "Interop.Client-receiver", new Source() { Dynamic = true }, OnReceiverAttached); if (receiverAttached.WaitOne(10000)) { // Receiver is attached. Logger.Debug("Receiver is attached"); var request = new Message(bytes) { Properties = new Properties() { // MessageId = "request" + 0, CorrelationId = replyTo, ReplyTo = replyTo, Subject = subject } }; sender.Send(request); var response = receiver.Receive(TimeSpan.FromSeconds(8)); if (null != response) { receiver.Accept(response); message = AmqpUtils.DeserializeMessage(response?.Body); } else { throw new ApplicationException( string.Format("Receiver timeout receiving response {0}", 0)); } } else { throw new ApplicationException("Receiver attach timeout"); } } catch (Exception e) { throw new ApplicationException("Exception: ", e); } finally { receiver?.Close(); sender?.Close(); session.Close(); } return(message); }
public void Restart() { isStarted = true; //CLose connections and channels if (subscriberChannel != null) { try { subscriberChannel.Close(); } catch { } } if (workChannel != null) { try { workChannel.Close(); } catch { } } if (conn != null) { try { conn.Close(); } catch { } try { conn.Dispose(); } catch { } } //TODO: CreateConnection() can always throw BrokerUnreachableException so keep that in mind when calling conn = connectionFactory.CreateConnection(); //Create shared queue SharedQueue <BasicDeliverEventArgs> queue = new SharedQueue <BasicDeliverEventArgs>(); //Create work channel and declare exchanges and queues workChannel = conn.CreateModel(); /* Work this into subscriber dispose and restart * //Cancel consumers on server * if(workCTag != null) * { * try * { * workChannel.BasicCancel(workCTag); * } * catch { } * } * * if (subscriberCTag != null) * { * try * { * workChannel.BasicCancel(subscriberCTag); * } * catch { } * } */ //Redeclare exchanges and queues AmqpUtils.DeclareExchangeAndQueues(workChannel, exchangeInfo, exchangeDeclareSync, subscriberId); //Listen on work queue workConsumer = new QueueingBasicConsumer(workChannel, queue); string workQueueName = AmqpUtils.GetWorkQueueName(exchangeInfo); workChannel.BasicConsume(workQueueName, false, workConsumer); //Listen on subscriber queue subscriberChannel = conn.CreateModel(); subscriberConsumer = new QueueingBasicConsumer(subscriberChannel, queue); string subscriberWorkQueueName = AmqpUtils.GetSubscriberQueueName(exchangeInfo, subscriberId); subscriberChannel.BasicConsume(subscriberWorkQueueName, false, subscriberConsumer); }