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 ); }
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; }
/// <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; }
public void RPCResponse(RequestData.ErrorCodeEnum code, string errorMessage) { this.RPCResponse(0, code, errorMessage, null, null); }
/// <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; }
/// <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; }
/// <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 ); }
/// <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 ); }
/// <summary> /// /// </summary> /// <param name="requestData"></param> protected virtual void OnMessage(RequestData requestData) { //no requests handling means we are client }
/// <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"); } }); }
public static ResponseData Error( RequestData.ErrorCodeEnum code,string message) { ResponseData response = new ResponseData (); response.ErrorCode = code; response.ErrorMessage = message; response.IsError = true; return response; }