Exemple #1
0
        protected override void OnMessage(RequestData requestData)
        {
            //you decide where to send ack
            requestData.Ack(true,false);

            ReceivedAsync = true;

            base.OnMessage(requestData);
        }
        /// <summary>
        /// Default SendMessage should provide performance statistics. Default SendMessage should be <generic/> and be reused.
        /// </summary>
        /// <param name="sourceAddress"></param>
        /// <param name="destAddress"></param>
        /// <param name="op"></param>
        /// <param name="parameters"></param>
        /// <param name="correlationID"></param>
        /// <param name="retVal"></param>
        /// <returns></returns>
        public bool SendMessageImpl( RequestData req )
        {
            try
            {
                MapMessageBuilder builder = new MapMessageBuilder ( this.OutboundChannel );

                #region copy dictionary to headers
                //there is no other way than
                if (req.Data != null)
                    foreach (DictionaryEntry entry in req.Data)
                    {
                        builder.Headers.Add ( (string)entry.Key, entry.Value );
                    }

                //map response
                if (req.Response != null)
                {
                    var parameters = req.Response.Serialize ();
                    if (parameters != null)
                        foreach (DictionaryEntry entry in parameters)
                        {
                            builder.Headers.Add ( (string)entry.Key, entry.Value );
                        }
                }
                #endregion

                //write body
                if(req.Body !=  null)
                    builder.RawWrite(Encoding.UTF8.GetBytes(req.Body));

                //who is publishing - it can be used to determine return route
                builder.Properties.AppId = req.SourceAddress;

                //each message has id for traceability
                builder.Properties.MessageId = Guid.NewGuid ().ToString ();

                //this cid will be used for response
                if (!string.IsNullOrEmpty ( req.CorrelationId))
                    builder.Properties.CorrelationId = req.CorrelationId;

                //what message we want to send
                builder.Headers["op"] = req.Op;

                //time of sending
                builder.Headers["sent"] = DateTime.Now.ToString ( "o" );

                //timeout; will help second party to cancel reposnse thus limitting traffic
                builder.Headers["timeout"] = 0;

                bool confirmed;

                //have to block because it is concurent access to this resource
                lock (this.OutboundChannel)
                {
                    //publish to topic exchange where routin key=destination process address, mandatory=true
                    this.OutboundChannel.BasicPublish ( this.Exchange, req.DestAddress, true, builder.Properties, RMQBus.GetBodyBytes(req.Body) );

                    //sometimes it blocks, maybe I should use timeout and resend? if the second party gets message second time then it should send back the same response
                    confirmed = this.OutboundChannel.WaitForConfirms ();
                }

                if (confirmed)
                    Tracer.TraceEvent ( System.Diagnostics.TraceEventType.Verbose, 0, "[{0}] op '{1}' dest. {2} value {3} cid {4}", this, req.Op, req.DestAddress, req.Response, req.CorrelationId );
                else
                    Tracer.TraceEvent ( System.Diagnostics.TraceEventType.Error, 0, "[{0}] not confirmed op '{1}' dest. {2} value {3} cid {4}", this, req.Op, req.DestAddress, req.Response, req.CorrelationId );

                return confirmed;
            }
            catch (Exception ex)
            {
                Tracer.TraceEvent ( System.Diagnostics.TraceEventType.Error, 0, "{0} send message exception {1}", req.SourceAddress, ex );
                return false;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="destAddress"></param>
        /// <param name="op"></param>
        /// <param name="session"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<ResponseData> RPC( RequestData req )
        {
            req.CorrelationId   = Guid.NewGuid ().ToString ();

            #region RPC request can come back before SendMessage comes back so I have to await for correlation id before sendmessage()

            TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object> ();
            //remember the correlation id, later we will trigger continuation based on cid
            Bus.RPCTaskCompletions.TryAdd ( req.CorrelationId, taskCompletionSource );
            //return continuation task which will process response for from the correlation task
            //RPCFinalize takes Task<object> and returns standard error code
            Task<ResponseData> RPCTask = taskCompletionSource.Task.ContinueWith<ResponseData> ( Bus.RPCFinalize );

            #endregion

            this.SendMessageImpl ( req );

            return RPCTask;
        }
        /// <summary>
        /// Where all messages from the service bus come.
        /// <para>The same connection thread is used</para>
        /// </summary>
        /// <param name="consumerTag"></param>
        /// <param name="deliveryTag"></param>
        /// <param name="redelivered"></param>
        /// <param name="exchange"></param>
        /// <param name="routingKey"></param>
        /// <param name="properties"></param>
        /// <param name="body"></param>
        public override void HandleBasicDeliver( string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, IBasicProperties properties, byte[] body )
        {
            //CHECK RabbitMQ.Client.Impl.WireFormatting.ReadFieldValue() how .net types are mapped to the AMQP
            BasicDeliverEventArgs Response = new BasicDeliverEventArgs ( consumerTag, deliveryTag, redelivered, exchange, routingKey, properties, body );

            string op=null;
            //op has to be always present, if not then wrap request into error request
            try
            {
                if (!Response.BasicProperties.Headers.ContainsKey( "op" ))
                {
                    //op does not exist
                    string em = string.Format ( "missing op parameter, use 'error' instead" );
                    op = "error";
                    Tracer.TraceEvent ( TraceEventType.Error, 0, "[{0}] {1}", this, em );
                }
                else
                {
                    if (!(Response.BasicProperties.Headers["op"] is byte[]))
                    {
                        //op is not byte[] hence string
                        string em = string.Format ( "op parameter wrong type, use 'error' instead" );
                        op = "error";
                        Tracer.TraceEvent ( TraceEventType.Error, 0, "[{0}] {1}", this, em );
                    }
                    else
                    {
                        op = Encoding.UTF8.GetString ( (byte[])Response.BasicProperties.Headers["op"] );
                    }
                }
                //Tracer.TraceEvent ( TraceEventType.Verbose, 0, "[{0}] received '{1}' key {2} dtag {3}", this, op, routingKey, deliveryTag );
            }
            catch (Exception ex)
            {
                Tracer.TraceEvent ( TraceEventType.Error, 0, "[{0}] exception {1}", this, ex.Message);
                op = "error";
            }

            var data = RMQBus.ConvertHeaders ( Response.BasicProperties.Headers );
            //try to build the response object
            ResponseData responseData = null;
            try
            {
                string em           =   "";
                #warning handle deerialization error properly or let the interface to handle it?
                responseData        =   ResponseData.Deserialize (data, body, ref em );
            }
            catch { }
            //create the object that will be used to dispatch, ack and eventually response to rpc request
            RequestData req = new RequestData(this)
            {
                //interface/adddress/topic/queue, routing key as destination address
                DestAddress = routingKey,
                //can be used to send response back, for now (05/02) we don't use "reply_to" header because we don't always have to
                SourceAddress = Response.BasicProperties.AppId,
                //mandatory, it's method name
                Op = op,
                //
                CorrelationId = Response.BasicProperties.CorrelationId,
                //response object is always present, if the message is not response then this object provides basic container api
                Response = responseData,
                //we need delivery tag to provide ack to RMQ
                DeliveryTag = deliveryTag
            };
            Tracer.TraceEvent(TraceEventType.Verbose, 0, "[{0}] recv. '{1}' key {2} dtag {3} req. {4}", this, op, routingKey, deliveryTag, req);
            //call b.logic process
            Process.OnPreMessage(req );
        }
Exemple #5
0
        public static RequestData Create(BasicReturnEventArgs args )
        {
            Hashtable headers    =   ConvertHeaders(args.BasicProperties.Headers);

            var responseData = new ResponseData()
            {
                ErrorCode       = (RequestData.ErrorCodeEnum)args.ReplyCode,
                ErrorMessage    = args.ReplyText
            };

            var requestData = new RequestData()
            {
                Body = Encoding.UTF8.GetString(args.Body),
                SourceAddress = args.BasicProperties.AppId,
                CorrelationId = args.BasicProperties.CorrelationId,
                Op = (string)headers["op"],
                Data = headers,
                DestAddress = args.RoutingKey,
                Response    =   responseData
            };

            return requestData;
        }
Exemple #6
0
        /// <summary>
        /// If this script is triggered by a rpc request then we have to generate rpc response. This methods helps to achieve that.
        /// </summary>
        /// <param name="value"></param>
        public void RPCResponse(int value, RequestData.ErrorCodeEnum code, string errorMessage, string body, Hashtable data)
        {
            //before we send response we can send ack
            this.Ack(true, false);

            //have we responded yet?
            if (!this.RPCResponded && !this.IsRPCRequest())
                return;

            Bus.SendMessage(this.DestAddress, this.SourceAddress, "rpc_response", this.CorrelationId, new ResponseData()
                {
                    ErrorCode = code,
                    Value = value,
                    ErrorMessage = errorMessage,
                    IsError = (code != RequestData.ErrorCodeEnum.Ok) ? true : false,
                    Body    =   body,
                    Data    =   data
                }
                );

            RPCResponded = true;
        }
Exemple #7
0
 public void RPCResponse(RequestData.ErrorCodeEnum code, string errorMessage)
 {
     this.RPCResponse(0, code, errorMessage, null, null);
 }
Exemple #8
0
 /// <summary>
 /// Used when handle faulty rps request for example when there is no handler/script to handle it but we want to response or log it anyway as there is a waiting client.
 /// </summary>
 /// <param name="op"></param>
 /// <param name="req"></param>
 /// <param name="error"></param>
 /// <param name="message"></param>
 /// <returns></returns>
 public static RequestData Create( string op, RequestData req, ErrorCodeEnum error, string message)
 {
     RequestData newReq              =   new RequestData ( req.CommunicationObject ) { Op = op };
     newReq.InnerRequestData         =   req;
     newReq.ErrorCode                =   error;
     newReq.ErrorMessage             =   message;
     //we need these fields to do rpc response
     newReq.CorrelationId            =   req.CorrelationId;
     newReq.DestAddress              =   req.DestAddress;
     newReq.DeliveryTag              =   req.DeliveryTag;
     return newReq;
 }
Exemple #9
0
        /// <summary>
        /// TaskContinuationSources are kept in Broker. This function is called by DipatchMessage() to eventually finalize RPC request.
        /// <para>All rpc continuations are directed to processes/queues.</para>
        /// </summary>
        /// <param name="op"></param>
        /// <param name="correlationID"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool TryHandleAsRPCResponse( ServiceBusClient process, RequestData request )
        {
            if (request.Op == "rpc_response" || request.Op == "offer_accepted" || request.Op == "transfer_ack")
            {
                if (request.CorrelationId == null)
                {
                    //error
                    request.Ack(true, false);
                    Tracer.TraceEvent(TraceEventType.Error, 0, "[{0}] missing rpc task continuation cid, message dropped {1}", process, request.ToString());
                    return true;
                }
                TaskCompletionSource<object> tcs;
                //tracer...
                if (RPCTaskCompletions.TryRemove(request.CorrelationId, out tcs))
                {
                    try
                    {
                        tcs.TrySetResult(request.Response);
                        Tracer.TraceEvent(TraceEventType.Verbose, 0, "[{0}] rpc response op {1} cid {2} response {3}", process, request.Op, request.CorrelationId, request.Response);
                    }
                    catch (Exception ex)
                    {
                        Tracer.TraceEvent(TraceEventType.Error, 0, "[{0}] rpc response op {1} cid {2} response {3} failed dute to {4}", process, request.Op, request.CorrelationId, request.Response, ex.Message);
                    }

                    request.Ack(true, false);
                    return true;
                }
                else
                {
                    //probably result was already set, how did it happen that there are two responses to one request? are they different? possibly not but they could come because of network failue
                    //preserving at-least-once semantics; we ack and drop, making use of indemptodency
                    request.Ack(true, false);
                    Tracer.TraceEvent(TraceEventType.Error, 0, "[{0}] missing rpc task continuation for rpc response {1}, message dropped ", process, request.ToString());
                    return true;
                }
            }
            return false;
        }
Exemple #10
0
 /// <summary>
 /// Send a message from one entity to another either over internal or external service bus.
 /// Dispatch message asynchronously.
 /// Represents fire&forget or RPC response.
 /// </summary>
 /// <param name="destAddress"></param>
 /// <param name="destAddress"></param>
 /// <param name="messageContent"></param>
 /// <returns></returns>
 public static void SendMessage( string sourceAddress, string destAddress, string op, string correlationId, ResponseData response )
 {
     RequestData requestData = requestData = new RequestData ( null )
     {
         Op = op
         ,
         SourceAddress = sourceAddress
         ,
         CorrelationId = correlationId
         ,
         Response = response
         ,
         DestAddress = destAddress
     };
     //address has to match registered comm object in order to send this message out. In other words an entity can be addressable locally and globally.
     RMQServiceBusInterface busProc = (RMQServiceBusInterface)RMQBus.GetClient(sourceAddress);
     //try to get sender's busprocess to use it's channels
     if (busProc == null)
     {
         Tracer.TraceEvent ( TraceEventType.Error, 0, "[{0}] no global process visibility, no local destination found {1}, message dropped {2}", sourceAddress, destAddress, requestData );
         return;
     }
     busProc.SendMessageImpl ( requestData );
 }
Exemple #11
0
        /// <summary>
        /// Generic way of doing entity to entity communnication.<para>
        /// If the second entity exists in this OS process it means the second entity is another process and we can do inproc communication as inproc service bus.</para>
        /// </summary>
        /// <param name="destAddress"></param>
        /// <param name="op"></param>
        /// <param name="session"></param>
        /// <returns></returns>
        public static Task<ResponseData> RPCRequest(string sourceAddress, string destAddress, string op, Hashtable data, CancellationToken token = new CancellationToken())
        {
            RequestData requestData = new RequestData ( null )
            {
                SourceAddress = sourceAddress,
                DestAddress = destAddress,
                Op = op,
                Token = token,
                Data    =   data
            };

            RMQServiceBusInterface busClient = (RMQServiceBusInterface)RMQBus.GetClient(sourceAddress);
            //find bus process in order to use its channels
            if (busClient == null)
            {
                Tracer.TraceEvent(TraceEventType.Error, 0, "[{0}] no global process visibility, no local destination found {1}, message dropped {2}", sourceAddress, destAddress, requestData);
                return null;
            }
            //we pass session to serialize it and sent over the wire
            return busClient.RPC ( requestData );
        }
Exemple #12
0
 /// <summary>
 /// 
 /// </summary>
 /// <param name="requestData"></param>
 protected virtual void OnMessage(RequestData requestData)
 {
     //no requests handling means we are client
 }
Exemple #13
0
        /// <summary>
        /// Here we do initial processing of requestData.
        /// <para>Processing of requestData can be passed to another object. Both for local and global messaging. Currenlty all processing is done in Entity. 
        /// Entity uses Addresses to register in RMQ.
        /// </para>        
        /// <para> Override this function to provide other threading models and remeber that this function is executed in the RMQ driver thread- switch ASAP.</para>
        public virtual void OnPreMessage(RequestData requestData)
        {
            //now everything is dispatched in thread pool
            ThreadPool.QueueUserWorkItem((r) =>
            {
                //try release pending rpc
                if (Bus.TryHandleAsRPCResponse(this, requestData))
                    return;

                try
                {
                    OnMessage(requestData);
                }
                catch (Exception ex)
                {
                    //todo: log it
                    //make sure that ack and RPC response (if necessary) is sent back
                    requestData.RPCResponse(1, RequestData.ErrorCodeEnum.InternalError, ex.Message, null, null);
                }
                finally
                {
                    //default response
                    requestData.RPCResponse(RequestData.ErrorCodeEnum.Error, "default response");
                }
            });
        }
Exemple #14
0
 public static ResponseData Error( RequestData.ErrorCodeEnum code,string message)
 {
     ResponseData response   =   new ResponseData ();
     response.ErrorCode      =   code;
     response.ErrorMessage = message;
     response.IsError = true;
     return response;
 }