What is the overhead of creating a new HttpClient per call in a WebAPI client?
Asked Answered
C

7

192

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);
    }
}
Copperplate answered 21/3, 2014 at 14:1 Comment(2)
I'm not sure, you could use the Stopwatch class to benchmark it, however. My estimation would be it makes more sense to have a single HttpClient, assuming all those instances are used in the same context.Carnotite
Related: #11178720, #15705592.Survance
P
247

HttpClient has been designed to be re-used for multiple calls. Even across multiple threads. The HttpClientHandler has Credentials and Cookies that are intended to be re-used across calls. Having a new HttpClient instance requires re-setting up all of that stuff. Also, the DefaultRequestHeaders 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 add HttpMessageHandlers 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 disposes HttpClientHandler, which then forcibly closes the TCP/IP connection in the pool of connections that is managed by ServicePointManager. This means that each request with a new HttpClient requires re-establishing a new TCP/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.

Painkiller answered 21/3, 2014 at 14:18 Comment(20)
which then forcibly closes the TCP/IP connection in the pool of connections that is managed by ServicePointManager How sure are you about this statement? That is hard to believe. HttpClient looks to me like a unit-of-work that is supposed to be instantiated often.Guildsman
I've also ran some tests on a LAN, as suggested by @matthew. I couldn't either see any considerable difference between the two approaches.Copperplate
@bruno I'd be interested to know if you can repro my 40% overhead on internet requests.Painkiller
Another good answer from @DarrelMiller if you find you're changing the DefaultRequestHeaders for each request #23522126Maddi
@DarrelMiller, I now use Singleton to keep a single instance of HttpClient. Question: can it be used in case HttpClient has to be created using HttpClientHandler? Currently I have var cookieContainer = new CookieContainer(); using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer }) using (var client = new HttpClient(handler)) { // .... }Frangipani
@Frangipani Yes you can still reuse an instance of HttpClient even if you created it with a new HttpClientHandler. Also note there is a special constructor for HttpClient that allows you to reuse a HttpClientHandler and dispose the HttpClient without killing the connection.Painkiller
@DarrelMiller, so is a better approach to use "using (var client = new HttpClient(handler, false))"? I would then need to keep a handler as singleton to avoid killing a connection, right?Frangipani
@Frangipani I prefer to keep the HttpClient around, but if you prefer keeping the HttpClientHandler around, it will keep the connection open when the second parameter is false.Painkiller
@DarrelMiller So it sounds like the connection is bound to the HttpClientHandler. I know that to scale I don't want to destroy the connection so I need to either keep a HttpClientHandler around and create all of my HttpClient instances from that OR create a static HttpClient instance. However, If the CookieContainer is bound to the HttpClientHandler, and my cookies need to differ per request, what do you recommend? I'd like to avoid thread synchronization on a static HttpClientHandler by modifying its CookieContainer for each request.Fahlband
@DarrelMiller Great answer! Perhaps you could mention the stale DNS issue one could face if one kept an instance of HttpClient for the lifetime of one's application (github.com/dotnet/corefx/issues/11224)?Survance
Is it the same for SmptClient or System.ServiceModel.ClientBase subclasses? Should I just use the same one to make multiple calls?Kiki
@Kiki I don't think you can draw an conclusions about those clients from HttpClient they are completely independent.Painkiller
I don't see how a single HttpClient instance could at all be thread safe. One thread come in and adds some headers, while another thread is clearing headers. Then the first thread sends. Wouldn't your headers be gone at that point?Llano
@Llano Don't mess with the DefaultRequestHeaders when you are working with multiple threads. Set them once and don't change them. If you want to change headers per request then create an HttpRequestMessage instance and use SendAsync().Painkiller
@Darrel Miller Thank for the explanation. I saw that recommendation in the solution below. Good to know. I will be updating my API packages soonLlano
Official documentation that supports this answer and confirms this class is intended to be instantiated once per application. learn.microsoft.com/en-us/dotnet/api/…Equi
@Equi ** keep an instance of HttpClient for the lifetime of your application** ... Does this mean i should define a static property of HttpClient in Application_Start of Global.asax.cs?Pteridology
@Pteridology You could. It would be better to register it as a singleton in the service collection and access it that way.Painkiller
@Darrel Miller i am using ASP.NET MVC 5 ... not .NET CorePteridology
@DarrelMiller How to deal with Pool of HttpClient (PoolingNHttpClientConnectionManager) where some will run with SSLContext (with clientcertifcate) and some without SSLContext? Do we have some way to set SSLContext while make execute call?Aeri
F
87

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 folks 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 a using() 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:

HttpClient is intended to be instantiated once and re-used throughout the life of an application. Especially in server applications, creating a new HttpClient instance for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.

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 the HttpRequestMessage manually and set those values on the HttpRequestMessage. Then, use the HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

UPDATE for .NET Core: You should use the IHttpClientFactory via Dependency Injection to create HttpClient instances. It will manage the lifetime for you and you do not need to explicitly dispose it. See Make HTTP requests using IHttpClientFactory in ASP.NET Core

Fahlband answered 27/1, 2016 at 18:31 Comment(2)
this post contains useful insight for those who will do stress testing .. !Pteridology
Nice callout on exhausting the sockets. My team was encountering a No file descriptors available error, because we were creating a new HTTP client on every request.Windage
S
10

As the other answers state, HttpClient is meant for reuse. However, reusing a single HttpClient instance across a multi-threaded application means you can't change the values of its stateful properties, like BaseAddress and DefaultRequestHeaders (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 the HttpClient methods you need (GetAsync, PostAsync etc) and delegates them to a singleton HttpClient. However that's pretty tedious (you will need to wrap the extension methods too), and fortunately there is another way - keep creating new HttpClient instances, but reuse the underlying HttpClientHandler. Just make sure you don't dispose the handler:

HttpClientHandler _sharedHandler = new HttpClientHandler(); //never dispose this
HttpClient GetClient(string token)
{
    //client code can dispose these HttpClient instances
    return new HttpClient(_sharedHandler, disposeHandler: false)         
    {
       DefaultRequestHeaders = 
       {
            Authorization = new AuthenticationHeaderValue("Bearer", token) 
       } 
    };
}
Survance answered 8/7, 2017 at 15:28 Comment(10)
The better way to go is keep one HttpClient instance, and then create your own local HttpRequestMessage instances and then use the .SendAsync() method on the HttpClient. This way it will still be thread-safe. Each HttpRequestMessage will have its own Authentication/URL values.Lucrative
@TimP. why is it better? SendAsync is much less convenient than the dedicated methods such as PutAsync, PostAsJsonAsync etc.Survance
SendAsync let's you change the URL and other properties such as headers and still be thread-safe.Lucrative
@TimP. yes but it's not as convenient. By sharing the handler you get thread safety and all the dedicated methods.Survance
@ohad do you want convenience or an application that scales? You can easily write a method that builds HttpRequestMessage instances and sends them off thru SendAsync with a static HttpClient.Fahlband
@DaveBlack sharing the handle is just as scalable, I don't see your point.Survance
Yes, the handler is the key. As long as that is shared between the HttpClient instances you are fine. I misread your earlier comment.Fahlband
If we keep a shared handler, do we still need to take care of stale DNS issue?Orchidectomy
@Orchidectomy reusing the handler does not resolve the DNS issue. Have to do something like this byterot.blogspot.com/2016/07/singleton-httpclient-dns.html to resolve DNSMedicable
Here is a gist that gives you a reusable http client along with time outs and exponential back off gist.github.com/odyth/3a5d3d72cc98f280f213005ec9a08de9Medicable
G
5

Related to high-volume web sites but not directly to HttpClient. We have the snippet of code below in all of our services.

        // number of milliseconds after which an active System.Net.ServicePoint connection is closed.
        const int DefaultConnectionLeaseTimeout = 60000;

        ServicePoint sp =
                ServicePointManager.FindServicePoint(new Uri("http://<yourServiceUrlHere>"));
        sp.ConnectionLeaseTimeout = DefaultConnectionLeaseTimeout;

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.

Gorden answered 4/4, 2017 at 16:31 Comment(2)
You still put a great deal of load on the server by opening and closing connections. If you use instance-based HttpClients with instance-based HttpClientHandlers, you still will run into port-exhaustion if you are not careful.Fahlband
Not disagreeing. Everything is a tradeoff. For us following a re-routed CDN or DNS is money in the bank vs. lost revenue.Gorden
R
2

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.

Retinite answered 31/10, 2018 at 21:35 Comment(0)
D
1

You may also want to refer to this blog post by Simon Timms: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

But HttpClient is different. Although it implements the IDisposable interface it is actually a shared object. This means that under the covers it is reentrant) and thread safe. Instead of creating a new instance of HttpClient for each execution you should share a single instance of HttpClient for the entire lifetime of the application. Let’s look at why.

Dyestuff answered 20/6, 2017 at 17:41 Comment(0)
O
0

As a first issue, while this class is disposable, using it with the using statement is not the best choice because even when you dispose HttpClient object, the underlying socket is not immediately released and can cause a serious issue named ‘sockets exhaustion.

But there’s a second issue with HttpClient that you can have when you use it as singleton or static object. In this case, a singleton or static HttpClient doesn't respect DNS changes.

in .net core you can do the same with HttpClientFactory something like this:

public interface IBuyService
{
    Task<Buy> GetBuyItems();
}
public class BuyService: IBuyService
{
    private readonly HttpClient _httpClient;

    public BuyService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Buy> GetBuyItems()
    {
        var uri = "Uri";

        var responseString = await _httpClient.GetStringAsync(uri);

        var buy = JsonConvert.DeserializeObject<Buy>(responseString);
        return buy;
    }
}

ConfigureServices

services.AddHttpClient<IBuyService, BuyService>(client =>
{
     client.BaseAddress = new Uri(Configuration["BaseUrl"]);
});

documentation and example at here

Ostracize answered 5/2, 2020 at 10:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.