Reverse Proxy in ASP.NET Web API

Updated: Be sure to check my follow up post

Due to infrastructure limitations my current team was in need of a reverse proxy that could point to our ASP.NET Web API endpoint. After failing to get IT to setup a reverse proxy in the load balancer I ended up experimenting with a reverse proxy based on a simple implementation using Web API.

The first requirement is to intercept all requests made to the reverse proxy endpoint. Fortunately the Web API pipeline allows this via the DelegatingHandler:

public class ProxyHandler : DelegatingHandler{} 

public class WebApiConfig
{
    public static void Configure(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new ProxyHandler());
        config.Routes.MapHttpRoute("abe", "{*path}");
    }
}

The configuration above adds the ProxyHandler to the general pipeline thus allowing it to intercept all requests which are processed by the Web API pipeline. Then a single catch-all route is added to make sure all requests are processed by the pipeline.

In the proxy delegating handler all requests must now be forwarded to the desired location:

public class ProxyHandler : DelegatingHandler
{
    private async Task<HttpResponseMessage> RedirectRequest(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var redirectLocation = "http://localhost:61948";
        var localPath = request.RequestUri.LocalPath; 

        var client = new HttpClient(); 

        var clonedRequest = await HttpRequestMessageExtensions.CloneHttpRequestMessageAsync(request); 

        clonedRequest.RequestUri = new Uri(redirectLocation + localPath); 

        return await client.SendAsync(clonedRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
    } 

    protected override
        Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        return RedirectRequest(request, cancellationToken);
    }
}

I experienced some problems forwarding GET requests which is why the above code clone the entire HttpRequestMessage via the below snippet found on stack overflow:

public static class HttpRequestMessageExtensions
{
    public static async Task<HttpRequestMessage> CloneHttpRequestMessageAsync(HttpRequestMessage req)
    {
        var clone = new HttpRequestMessage(req.Method, req.RequestUri); 

        var ms = new MemoryStream();
        if (req.Content != null)
        {
            await req.Content.CopyToAsync(ms).ConfigureAwait(false);
            ms.Position = 0; 

            if ((ms.Length > 0 || req.Content.Headers.Any()) && clone.Method != HttpMethod.Get)
            {
                clone.Content = new StreamContent(ms); 

                if (req.Content.Headers != null)
                    foreach (var h in req.Content.Headers)
                        clone.Content.Headers.Add(h.Key, h.Value);
            }
        } 

        clone.Version = req.Version; 

        foreach (var prop in req.Properties)
            clone.Properties.Add(prop); 

        foreach (var header in req.Headers)
            clone.Headers.TryAddWithoutValidation(header.Key, header.Value); 

        return clone;
    }
}

I’m very impressed by the elegance of both Web API but more so the way the HttpRequestMessage/HttpResponseMessage is reused between Web API and HttpClient.

I tested the reverse proxy solutions against our current API and all our GET/POST requests went through. Furthermore all exception message was passed through the proxy as well.