Esempio n. 1
0
        /// <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.
        }
Esempio n. 2
0
        /// <summary>
        /// Initializes a new instance of the <see cref="T:RestBus.RabbitMQ.RestBusClient" /> class.
        /// </summary>
        /// <param name="messageMapper">The <see cref="IMessageMapper" /> the client uses to route messages.</param>
        /// <param name="settings">Client settings.</param>
        public RestBusClient(IMessageMapper messageMapper, ClientSettings settings) : base(new HttpClientHandler(), true)
        {
            //Set default HttpClient related fields
            timeout = TimeSpan.FromSeconds(100);
            MaxResponseContentBufferSize = int.MaxValue;
            //TODO: Setup cancellation token here.

            //Ensure messageMapper server uris are valid
            AmqpConnectionInfo.EnsureValid(messageMapper.ServerUris, "messageMapper.ServerUris");

            //Configure RestBus fields/properties
            this.messageMapper   = messageMapper;
            this.messagingConfig = messageMapper.MessagingConfig; //Fetched only once.
            if (messagingConfig == null)
            {
                throw new ArgumentException("messageMapper.MessagingConfig returned null");
            }

            //Set ClientSettings
            this.Settings        = settings ?? new ClientSettings(); // Always have a default instance, if it wasn't passed in.
            this.Settings.Client = this;                             //Indicate that the settings is owned by this client.

            //Instantiate connection manager and RPC strategies;
            connectionMgr    = new ConnectionManager(messageMapper.ServerUris);
            directStrategy   = new DirectReplyToRPCStrategy();
            callbackStrategy = new CallbackQueueRPCStrategy(this.Settings, messageMapper.GetServiceName(null));
        }
Esempio n. 3
0
        /// <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() };

            AmqpConnectionInfo.EnsureValid(messageMapper.ServerUris, "messageMapper.ServerUris");
            this.connectionFactory = new ConnectionFactory();
            connectionFactory.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.
        }
Esempio n. 4
0
        /// <summary>
        /// Initializes a new instance of the <see cref="T:RestBus.RabbitMQ.RestBusClient" /> class.
        /// </summary>
        /// <param name="messageMapper">The <see cref="IMessageMapper" /> the client uses to route messages.</param>
        /// <param name="settings">Client settings.</param>
        public RestBusClient(IMessageMapper messageMapper, ClientSettings settings ) : base(new HttpClientHandler(), true)
        {
            //Set default HttpClient related fields
            timeout = TimeSpan.FromSeconds(100);
            MaxResponseContentBufferSize = int.MaxValue;
            //TODO: Setup cancellation token here.

            //Ensure messageMapper server uris are valid
            AmqpConnectionInfo.EnsureValid(messageMapper.ServerUris, "messageMapper.ServerUris");

            //Configure RestBus fields/properties
            this.messageMapper = messageMapper;
            this.messagingConfig = messageMapper.MessagingConfig; //Fetched only once.
            if (messagingConfig == null) throw new ArgumentException("messageMapper.MessagingConfig returned null");

            //Set ClientSettings
            this.Settings = settings ?? new ClientSettings(); // Always have a default instance, if it wasn't passed in.
            this.Settings.Client = this; //Indicate that the settings is owned by this client.

            //Instantiate connection manager and RPC strategies;
            connectionMgr = new ConnectionManager(messageMapper.ServerUris);
            directStrategy = new DirectReplyToRPCStrategy();
            callbackStrategy = new CallbackQueueRPCStrategy(this.Settings, messageMapper.GetServiceName(null));
        }
Esempio n. 5
0
        /// <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);
                }
            }
        }