Efficiently Forwarding HTTP Requests with IHttpAsyncHandler

I am developing an HTTP front controller based on the Martin Fowler pattern ( link ). In my case the controller has the following responsibilities: - Unmarshall encapsulated data - Authorize the request - Logging - Relay / forward the request to another server

The following came to mind: - (synchronous) IHttpHandler, forwarding request using WebClient or HttpWebRequest class - (asynchronous) IHttpListener (non-IIS solution) - (asynchronous) IHttpAsyncHandler

Ideal situation: FC can handle many concurrent requests (> 10000 TPS) without writing the CPU.

To test the solutions, I created a small framework that has 3 clients making requests, a front controller that sits on the middle, and 2 servers that respond to requests sent to FC. Framework Test Scenario 3, firstly it tests fast responses with small payloads, secondly: fast responses with large payloads (> 100KB) and finally its tests with slow responses (> 3 seconds) and small payloads loads.

Operations per second (TPS) is dropping to a trailing low (<25 TPS) with the last test with synchronous HTTP handlers. I am guessing this is because the handler is blocking the thread when it expects a response. To overcome this problem, I started implementing an asynchronous handler (see code below). The problem is, it just doesn't work.

The last (untested) solution that came to mind is to use the HttpListener class. Assuming this is a lighter solution with fine grain control for concurrency. I saw an example implementation using the RX framework by Jose F. Romanello ( link ).

My question would be: why is the handler code not working ?, is this the most efficient way to do it? or should I be in favor of the HttpListener solution.

Asynchronous HTTP handler code:

public class ForwardRequestHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        var uri = GetForwardUriFor(context.Request.Url.PathAndQuery);

        var proxy = HttpWebRequest.Create(uri) as HttpWebRequest;
        return proxy.BeginGetResponse(new AsyncCallback(EndProcessRequest), new ForwardedRequestContext(context, proxy));
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        var proxy = result.AsyncState as ForwardedRequestContext;
        proxy.TransferResponse(result);
    }

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        throw new NotSupportedException();
    }

    private Uri GetForwardUriFor(string path)
    {
        var loadbalancer = new RoundRobinLoadBalancer();
        var endpoint = loadbalancer.GetRandomEndPoint();

        return new Uri(
            string.Format("http://{0}{1}", endpoint, path)
        );
    }
}

public class ForwardedRequestContext
{
    private readonly HttpContext context;
    private readonly HttpWebRequest forwarder;

    public ForwardedRequestContext(HttpContext context, HttpWebRequest forwarder)
    {
        this.context = context;
        this.forwarder = forwarder;
    }

    public void TransferResponse(IAsyncResult ar)
    {
        var response = GetResponse();

        var result = forwarder.EndGetResponse(ar);
        response.StatusCode = 200;
        response.ContentType = result.ContentType;
        response.AddHeader("Content-Length", result.ContentLength.ToString());

        result.GetResponseStream().CopyTo(response.OutputStream);
        response.Flush();

        result.Close();
    }

    private HttpResponse GetResponse()
    {
        return context.Response;
    }
}

      

+3


source to share


2 answers


One possible solution to this might be to use ARR to do the forwarding for you.

Here's an example of this on Azure:



https://github.com/richorama/AzureARR

0


source


in return proxy.BeginGetResponse(new AsyncCallback(EndProcessRequest), ...

you create a new callback and ASP.net does not get any information about the end of the request, so it cannot stop processing the request (this is at least what I have observed with many parallel requests). So use the callback passed as argument.

return proxy.BeginGetResponse(cb, ...

      



Greetings

+6


source







All Articles