A recent post by Darrel Miller, entitled “HttpClient, it lives, and it is glorious.” motivated me to investigate more about the HttpClient class underlying architecture.
This post is the result of that study, and focus mainly on design and organization aspects, namely extensibility.
For code examples, refer to Darrel’s excellent post.
The HttpClient class is the .NET (yet another) new client for the HTTP protocol, similar in purpose to HttpWebRequest and WebClient. It is being introduced with .NET 4.5, however there is also an implementation for .NET 4.0 associated with Web API (both source code and NuGet package).
The HttpClient class should not be viewed in isolation or as just a replacement for WebClient. It is part and takes advantage of a set of new classes for the HTTP protocol, present in the System.Net.Http namespace, which are the subject of the next section.
Core HTTP Abstractions
The HTTP protocol is an application level request-response protocol that operates by exchanging messages. The new HttpRequestMessage and HttpResponseMessages framework classes are used to represent these messages. A previous post described these classes on the Web API context. However, they are used outside of that context, namely on the client side.
The computation that produces response messages, given request messages, is represented by the abstract HttpMessageHandler class. To avoid blocking the calling thread, this computation signature is asynchronous, that is, a Task<HttpResponseMessage> is returned instead of a HttpResponseMessage. The “WCF Web API–Message Handlers” post described this new class in the Web API context. However this new class is not used exclusively by the Web API. Namely, similarly to HttpRequestMessage and HttpResponseMessage, this new class is in the System.Net.Http and not on a Web API namespace.
Frequently, messages handlers delegate the response computation to other handlers. Consider, for example, a caching intermediary. If a response to a request can be produced from cached information, then the caching intermediary produces the response directly. Otherwise, the caching component forwards the request message to another handler and caches the response information.
The class DelegatingHandler is used to represent this particular kind of response computation, where the forwarding handler reference is stored on an InnerHandler property.
The HttpClient class is used to create HTTP request messages, manage the request-response process and extract the response’s content. However, it delegates the actual sending of the request and the asynchronous response reception to a message handler.
Given the usage of a message handler, the HttpClient class may seem redundant. Namely, it is possible to explicitly create a request and use a message handler to obtain a response. However, the HttpClient class adds some relevant functionality:
- Set of helper methods that create requests and also process the responses (e.g. public Task<string> GetStringAsync(string requestUri)).
- Definition of a default header set, that is applied to all sent messages (DefaultRequestHeaders property).
- Timeout and cancellation management (e.g. Timeout property and CancelPendingRequests() method).
The .NET framwork provides two message handlers classes, that can be used with the HttpClient for sending requests: the HttpClientHandler class and the WebRequestHandler class. They both use the old HttpWebRequest internally. The major difference is that WebRequestHandler exposes HttpWebRequest functionality that isn’t available in all platforms (e.g. the Pipelined property is not available on Silverlight). On the other hand, HttpClientHandler should be usable on all platforms.
The handler used by a HttpClient instance is defined in its constructor. If absent, a HttpClientHandler instance is used by default.
A common technique is to explicitly create a client handler (e.g. HttpWebRequest), then explicitly set some of its properties and finally use it to create a HttpClient instance. Darrel’s Windows authentication example is an example of this technique.
The following diagram presents the relation between these classes.
Note that this architecture allows the composition of intermediary delegating handlers (e.g. client side caching), resulting in a handler pipeline.