POST Tunneling Middleware for ASP.NET Core

I wasn't expecting that I'll be writing a post about POST Tunneling in 2017 (almost 2018), I thought it's a thing of the past.

Recently a friend of mine reached out to me for advice. His company has delivered a new ASP.NET Core based service to a client. The service was exposing a Web API which (among others) relied on PATCH requests. After the deployment it turned out that one of older applications which were supposed to integrate with the new service wasn't able to issue PATCH requests due to technical limitations. I suggested they check if that old application can issue custom HTTP headers which would allow them to solve the problem with POST Tunneling.

What is POST Tunneling

POST Tunneling is a quite old technic. I've encountered it for the first time in 2012. Back then the issue was very common. A lot of HTTP clients (including XMLHttpRequest in some browsers) weren't providing support for all HTTP methods. Also many corporate networks infrastructures were blocking certain methods. The solution was to tunnel such method through POST request with help of custom header (I believe that X-HTTP-Method-Override was the most frequently used one). The server would examine the incoming POST request and if the header was present its value would be treated as the actual method.

Middleware implementation

The middleware should allow for configuring two things: the name of the custom header and list of methods which can be tunneled.

public class PostTunellingOptions
{
    public string HeaderName { get; set; }

    public IEnumerable<string> AllowedMethods { get; set; }
}

The implementation is very similar to SSL Acceleration Middleware I've done in the past. The heart is IHttpRequestFeature with its Method property. Changing value of that property will trick all the later steps of pipeline to use the new value.

public class PostTunnelingMiddleware
{
    private readonly RequestDelegate _next;

    private readonly string _headerName;
    private readonly HashSet<string> _allowedMethods;

    public PostTunnelingMiddleware(RequestDelegate next, IOptions options)
    {
        // Null checks removed for brevity

        _headerName = options.Value.HeaderName;

        _allowedMethods = new HashSet<string>();
        if (options.Value.AllowedMethods != null)
        {
            foreach (string allowedMethod in options.Value.AllowedMethods)
            {
                _allowedMethods.Add(allowedMethod.ToUpper());
            }
        }
    }

    public Task Invoke(HttpContext context)
    {
        if (HttpMethods.IsPost(context.Request.Method))
        {
            if (context.Request.Headers.ContainsKey(_headerName))
            {
                string tunelledMethod = context.Request.Headers[_headerName];
                if (_allowedMethods.Contains(tunelledMethod))
                {
                    IHttpRequestFeature httpRequestFeature = context.Features.Get<IHttpRequestFeature>();
                    httpRequestFeature.Method = tunelledMethod;
                }
            }
        }

        return _next(context);
    }
}

In order to add POST Tunneling to the application it's enough to register the middleware at the desired position in the pipeline.

public class Startup
{
    ...

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...

        app.UseMiddleware<PostTunnelingMiddleware>(Options.Create(new PostTunnelingOptions
        {
            HeaderName = "X-HTTP-Method-Override",
            AllowedMethods = new[] { HttpMethods.Patch }
        }));

        app.UseMvc();

        ...
    }
}

I've made the middleware (with some helper extensions) available as a Gist so if any of you ever end up with similar problem, it's out there ready to use.