What should be the HttpClient
lifetime of a WebAPI client?
Is it better to have one instance of the HttpClient
for multiple calls?
What's the overhead of creating and disposing a HttpClient
per request, like in example below (taken from http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client):
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:9000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// New code:
HttpResponseMessage response = await client.GetAsync("api/products/1");
if (response.IsSuccessStatusCode)
{
Product product = await response.Content.ReadAsAsync<Product>();
Console.WriteLine("{0}\t${1}\t{2}", product.Name, product.Price, product.Category);
}
}
HttpClient
has been designed to be re-used for multiple calls. Even across multiple threads. TheHttpClientHandler
has Credentials and Cookies that are intended to be re-used across calls. Having a newHttpClient
instance requires re-setting up all of that stuff. Also, theDefaultRequestHeaders
property contains properties that are intended for multiple calls. Having to reset those values on each request defeats the point.Another major benefit of
HttpClient
is the ability to addHttpMessageHandlers
into the request/response pipeline to apply cross cutting concerns. These could be for logging, auditing, throttling, redirect handling, offline handling, capturing metrics. All sorts of different things. If a new HttpClient is created on each request, then all of these message handlers need to be setup on each request and somehow any application level state that is shared between requests for these handlers also needs to be provided.The more you use the features of
HttpClient
, the more you will see that reusing an existing instance makes sense.However, the biggest issue, in my opinion is that when a
HttpClient
class is disposed, it disposesHttpClientHandler
, which then forcibly closes theTCP/IP
connection in the pool of connections that is managed byServicePointManager
. This means that each request with a newHttpClient
requires re-establishing a newTCP/IP
connection.From my tests, using plain HTTP on a LAN, the performance hit is fairly negligible. I suspect this is because there is an underlying TCP keepalive that is holding the connection open even when
HttpClientHandler
tries to close it.On requests that go over the internet, I have seen a different story. I have seen a 40% performance hit due to having to re-open the request every time.
I suspect the hit on a
HTTPS
connection would be even worse.My advice is to keep an instance of HttpClient for the lifetime of your application for each distinct API that you connect to.
If you want your application to scale, the difference is HUGE! Depending on the load, you will see very different performance numbers. As Darrel Miller mentions, the HttpClient was designed to be re-used across requests. This was confirmed by guys on the BCL team who wrote it.
A recent project I had was to help a very large and well-known online computer retailer scale out for Black Friday/holiday traffic for some new systems. We ran into some performance issues around the usage of HttpClient. Since it implements
IDisposable
, the devs did what you would normally do by creating an instance and placing it inside of ausing()
statement. Once we started load testing the app brought the server to its knees - yes, the server not just the app. The reason is that every instance of HttpClient opens a port on the server. Because of non-deterministic finalization of GC and the fact that you are working with computer resources that span across multiple OSI layers, closing network ports can take a while. In fact Windows OS itself can take up to 20 secs to close a port (per Microsoft). We were opening ports faster than they could be closed - server port exhaustion which hammered the CPU to 100%. My fix was to change the HttpClient to a static instance which solved the problem. Yes, it is a disposable resource, but any overhead is vastly outweighed by the difference in performance. I encourage you to do some load testing to see how your app behaves.You can also check out the WebAPI Guidance page for documentation and example at https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client
Pay special attention to this call-out:
If you find that you need to use a static
HttpClient
with different headers, base address, etc. what you will need to do is to create theHttpRequestMessage
manually and set those values on theHttpRequestMessage
. Then, use theHttpClient:SendAsync(HttpRequestMessage requestMessage, ...)
Related to high-volume web sites but not directly to HttpClient. We have the snippet of code below in all of our services.
From https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Net.ServicePoint.ConnectionLeaseTimeout);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true
"You can use this property to ensure that a ServicePoint object's active connections do not remain open indefinitely. This property is intended for scenarios where connections should be dropped and reestablished periodically, such as load balancing scenarios.
By default, when KeepAlive is true for a request, the MaxIdleTime property sets the time-out for closing ServicePoint connections due to inactivity. If the ServicePoint has active connections, MaxIdleTime has no effect and the connections remain open indefinitely.
When the ConnectionLeaseTimeout property is set to a value other than -1, and after the specified time elapses, an active ServicePoint connection is closed after servicing a request by setting KeepAlive to false in that request. Setting this value affects all connections managed by the ServicePoint object."
When you have services behind a CDN or other endpoint that you want to failover then this setting helps callers follow you to your new destination. In this example 60 seconds after a failover all callers should re-connect to the new endpoint. It does require that you know your dependent services (those services that YOU call) and their endpoints.
As the other answers state,
HttpClient
is meant for reuse. However, reusing a singleHttpClient
instance across a multi-threaded application means you can't change the values of its stateful properties, likeBaseAddress
andDefaultRequestHeaders
(so you can only use them if they are constant across your application).One approach for getting around this limitation is wrapping
HttpClient
with a class that duplicates all theHttpClient
methods you need (GetAsync
,PostAsync
etc) and delegates them to a singletonHttpClient
. However that's pretty tedious (you will need to wrap the extension methods too), and fortunately there is another way - keep creating newHttpClient
instances, but reuse the underlyingHttpClientHandler
. Just make sure you don't dispose the handler:One thing to point out, that none of the "don't use using" blogs note is that it is not just the BaseAddress and DefaultHeader that you need to consider. Once you make HttpClient static, there are internal states that will be carried across requests. An example: You are authenticating to a 3rd party with HttpClient to get a FedAuth token (ignore why not using OAuth/OWIN/etc), that Response message has a Set-Cookie header for FedAuth, this is added to your HttpClient state. The next user to login to your API will be sending the last person's FedAuth cookie unless you are managing these cookies on each request.
You may also want to refer to this blog post by Simon Timms: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/