Adding More Control to HttpClient Redirects
I'm a big fan of the .NET Framework. It's insane how much you can do without ever referencing a library outside of the base framework. One of my favorite additions to .NET 4.5 was the
HttpClient class... and pretty much everything else in the
System.Net.Http.WebRequest assemblies! Let's just move past the days of
WebClient and just forget it ever happened, hmkay? Now we can
async all the things!
I do have one small gripe about the default behavior of
HttpClient: the way they implemented HTTP redirects. Yes, yes, they work great. All of options to enable, disable, and limit the redirects are available on the
WebRequestHandler. La-de-da. Unfortunately, they put all of the logic of redirection at the lowest handler level (actually they put the code in
HttpWebRequest, but you're not meant to reference this class directly). Wait, what's wrong with that?
Well, first let's loop around and talk about another great part of
System.Net.Http: delegating handlers (
DelegatingHandler). These guys allow you to compose a pipeline of operations you'd like to perform on an HTTP request on its way out or an HTTP response on its way in. Custom caching, authentication, logging, special content-type handling, mutate headers, etc. The possibilities are limitless!
What's even better is this delegating handler stuff can be used on both the server side (with ASP.NET MVC and Web API) and client side. If you're interested in reading more about delegating handlers, check out K. Scott Allen's blog post about 'em.
Now imagine you have a URL the leads through one, two, or seven redirects. Also imagine you have a delegating handler set up in your
HttpClient that does some super helpful, super important logging:
As mentioned before, the HTTP redirect logic is done at the very lowest C# level. What this means is that a delegating handler you have configured into your HTTP client will only see the very first
HttpRequestMessage and the very last
HttpResponseMessage... and the last
myResponse.RequestMessage. None of the messages in between.
Ideally, the redirect would be resolved in a delegating handler. Yes, this would make the most common case (no delegating handlers and the need for browser-like redirects) a little more complicated to wire-up, but the flexibility that comes with wrapping this bit of logic up in a delegating handler has benefits. This would even give a nicely encapsulated means for resolving interesting problems like HTTP 300 Multiple Choices :).
Imagine taking it a step forward and splitting out even more tricky logic currently handled by
HttpWebRequest and putting it in different delegating handlers. Mix, match, compose. Interesting...
Well, I focused on the problem at hand and implemented a delegating handler that overrides the built-in redirects, allowing you to put redirection anywhere you want in your client pipeline.
For now the code is in a GitHub Gist. If I find myself needing it in a lot of different projects or some of my readers would like it in a more accessible form, I could consider wrapping it up in a NuGet package. Let me know in the comments below.
Oh, and by default
RedirectingHandler disables redirection in your inner
HttpClientHandler. There are some other options on the handler that are worth looking at. Also, mad props to Kenneth Reitz's Python
requests library, which I ported for the redirect rules.