How to Nak a ServiceStack RabbitMQ message within

2019-06-11 03:20发布

问题:

As a follow up to my original question, when throwing exceptions from my web service, they are converted by ServiceStack into more HTTP friendly response codes, which is problematic for the interested party, a windows service (MQ Logic Server) which only receives a 500 internal server error.

Is there a way to persist the OutBoundAgentNotFoundException in the inner exception property so that I can inspect it elsewhere and action off of it?

Web service endpoint:

//This is a callback that the phone system uses to notify us of what routing decision was made.
//It's now up to this method to assign it to the agent, or send it back to the queue via exceptions
public object Get(OutBoundILeadRequest request)
{
    if (request == null) throw new ArgumentNullException("request");

    _log.Debug("OutBoundILeadRequest: {0}".Fmt(request.ToJson()));

    // assign the agent to the lead

    // Agent phone extension is called, 1 and 2 are pressed, accepts call "Agent":"9339","Status":"0"
    // Agent phone extension is called, 1 and 2 are pressed, but no answer or wrong number "Agent":"9339","Status":"0"
    if (request.Status == "0" && !string.IsNullOrEmpty(request.Agent))
    {
        //assignment

    }


    // throw will send it back to the queue and then it will be re-tried, then it will be sent to the dlq  
    // https://stackoverflow.com/questions/27519209


    // Agent phone extension is called, but no response from Agent => "AgentsTalking":"0","Status":"3" 
    // this action puts them in NOT READY (Cisco Agent Desktop)            
    if (request.Status == "3" && !string.IsNullOrEmpty(request.AgentsTalking) && request.AgentsTalking == "0")
    {
        //retry

    }


    // No one assigned to this Phone Queue is in a ready state => "AgentsTalking":"number of agents on the phone","Status":"1" (Potentially can redistribute the phone)
    if (request.Status == "1" && !string.IsNullOrEmpty(request.AgentsTalking) && request.AgentsTalking != "0")
    {
        //retry
        throw new OutBoundAgentNotFoundException("<NEW MEWSSAGE HERE 1>");
    }

    // No one assigned to this Phone Queue is in a ready state => "AgentsTalking":"0","Status":"1" (No one in the Call Center)
    if (request.Status == "1" && !string.IsNullOrEmpty(request.AgentsTalking) && request.AgentsTalking == "0")
    {
        //retry
        throw new OutBoundAgentNotFoundException("<NEW MEWSSAGE HERE 2>");
    }
    // 'should' not get here 
    throw new OutBoundAgentNotFoundException("<NEW MEWSSAGE HERE 3>");
}

registration of the handler in the windows service:

mqServer.RegisterHandler<OutboundILeadPhone>(m =>
{
    var db = container.Resolve<IFrontEndRepository>();
    db.SaveMessage(m as Message);
    return ServiceController.ExecuteMessage(m);
}, PhoneApi.OnExceptionLeadInformation , 1);

windows service exception handler:

public static void OnExceptionLeadInformation(IMessage<OutboundILeadPhone> request, Exception exception)
{

      // Here is where I'd like to inspect the type of exception and Nak/requeue the Message

}

windows service endpoint:

//This method is being called in response to a message being published by the 
//RabittMQ Broker (Queue mq:OutboundILeadPhone.inq)
//The result of sucessuflully processing this message will result in a message being published to 
// (Queue mq:OutboundILeadPhone.outq)
public object Post(OutboundILeadPhone request)
{
    if (request == null) throw new ArgumentNullException("request");

    //By default there is only 1 worker thread (per message type) 
    //that's used to process the request, so you can just add a 
    //Thread.Sleep() in your Service to delay processing of the request 
    //as the next request only gets processed after the previous one has finished. 

    int delay = 2000;
    Thread.Sleep(delay); //todo:smp configurable

    var profiler = Profiler.Current;
    using (profiler.Step("PhoneApi DirectApiServices POST OutboundILeadPhone"))
    {
        try
        {





        }
        catch (Exception exception)
        {
            _log.Error(request.ToJson(), exception);
            throw;
        }

    }
    /*_log.Debug(profiler.GetTimingHierarchy());*/

    return null;
}

Thank you, Stephen

回答1:

I've just committed a change that will store the original exception in InnerException and the MQ Error handler now gets passed the MessageHandler processing the message so your error callback now has access to the original MqClient instance that retrieved the message, which can be used to NAK the message, e.g:

public CustomException LastCustomException;

public void HandleMqCustomException(IMessageHandler mqHandler, 
    IMessage<MqCustomException> message, Exception ex)
{
    LastCustomException = ex.InnerException as CustomException;

    bool requeue = !(ex is UnRetryableMessagingException)
        && message.RetryAttempts < 1;

    if (requeue)
    {
        message.RetryAttempts++;
    }

    message.Error = ex.ToResponseStatus();
    mqHandler.MqClient.Nak(message, requeue: requeue, exception: ex);
}

This is is from MqAppHostTests which shows how to retrieve the original exception from the Service and Nak the message from the original MQ Client instance.

This change is available from v4.0.35+ that's now available on MyGet.