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.