Singleton httpclient vs creating new httpclient request
Update: It seems that using a single static instance of HttpClient
doesn't respect DNS changes, so the solution is to use HttpClientFactory
. See here for Microsoft docs about it.
To use the HttpClientFactory
you have to use Microsoft's dependency injection. This is the default for ASP.NET Core projects, but for others you will have to reference Microsoft.Extensions.Http and Microsoft.Extensions.DependencyInjection.
Then when you're creating your service container, you simply call AddHttpClient()
:
var services = new ServiceCollection();
services.AddHttpClient()
var serviceProvider = services.BuildServiceProvider();
And then you can inject IHttpClientFactory
into your services, and behind the scenes HttpClientFactory
will maintain a pool of HttpClientHandler
objects - keeping your DNS fresh and preventing problems with connection pool exhaustion.
Old answer:
Singleton is the correct way to use HttpClient
. Please see this article for full details.
Microsoft docs state:
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors. Below is an example using HttpClient correctly.
And indeed, we found this in our application. We have code that can potentially make hundreds of API requests in a foreach
loop, and for each iteration we were creating an HttpClient
wrapped in a using
. We soon started getting red herring errors from our MongoClient
saying that it had timed out trying to connect to the database. After reading the linked article, we found that even after disposing of HttpClient
, and realised that we were exhausting the available sockets.
The only thing to note is that things like DefaultRequestHeaders
and BaseAddress
will be applied anywhere that HttpClient is used. As a singleton, this is potentially throughout the application. You can still create multiple HttpClient
instances in your application, but just be aware that each time you do, they create a new connection pool and, as such, should be created sparingly.
As pointed out by hvaughan3, you also can't change the instance of HttpMessageHandler
used by the HttpClient, so if this matters to you, you would need to use a separate instance with that handler.
HttpClient inject into Singleton
- Will it use
HttpClientFactory
to create an instance ofHttpClient
?
Yes. A default HttpClient
is registered as a transient service during HttpClientFactory registration.
- I guess it uses
HttpClientFactory
but will it have issues with DNS changes in that case?
Correct, it still would. As you inject it into a singleton, HttpClient
here will be created only once. In order to make use of HttpClientFactory
's HttpMessageHandler
pooling, you'd need your HttpClient
s to be short-lived. So, for this you would rather need to inject IHttpClientFactory
itself and call CreateClient
when you need one. (Note that short-living HttpClient
s only apply to HttpClientFactory
usage). BTW switching to a typed client will not help when injecting into a singleton, HttpClient
will still end up being created only once, see https://github.com/dotnet/runtime/issues/64034.
Also, you can actually avoid HttpClientFactory
entirely and still have DNS changes respected. For that you may have a static/singleton HttpClient
with PooledConnectionLifetime
set to some reasonable timeout (e.g. the same 2 minutes HttpClientFactory
does)
services.AddSingleton(() => new HttpClient(
new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(2) }
));
Is a Singleton HttpClient receiving a new HttpMessageHandler After X Minutes
does the internal HttpMessageHandler expires also when the wrapping HttpHandler is used as a Singleton?
WARNING: Keeping HttpClient
instances alive through Singletons is not safe.
The configured HttpMessageHandler
instance will expire after two minutes. Any HttpClient
that is created after the the expiry date will get a new HttpMessageHandler
containing a new expiry date. But this doesn't help a HttpClient
instance that is kept alive.
IMPORTANT: HttpClient
will keep using the same HttpMessageHandler
for as long as it lives and will, therefore,
not respect DNS changes. Only new HttpMessageHandler
instances will see DNS updates.
This means that as long as you keep the HttpClient
alive, that HttpClient
will miss DNS changes, which is why HttpClient
instances should only live for a short period of time. Creating and cleaning up HttpClient
instances is very cheap as long as you reuse the underlying HttpMessageHandler
instances (which is what the infrastructure does for you).
In your case, unfortunately, your HttpClient
is injected into the CatalogService
, which is injected into a Singleton
consumer. The Singleton
keeps the CatalogService
alive and, therefore, indirectly keeps the HttpClient
alive for the duration of the application—Your HttpClient
is now a Captive Dependency.
What you're experiencing is an unfortunate design flaw in the new .NET Core IHttpClientFactory
infrastructure. I consider this a flaw because IMO the infrastructure should prevent you from holding HttpClient
instances captive, e.g. by registering the client (your CatalogService
) as Scoped
. I reported this issue back in January 2019. Microsoft has acknowledged the problem but has, as of this writing, no solution available.
As there is no solution yet, you have to be very careful not to cause HttpClient
to be kept alive as Captive Dependency. This means you can't inject it into Singleton
consumers (even indirect one's).
A good way to prevent this is by registering the client (your CatalogService
) as Scoped
. This allows the framework's configuration system to validate whether it is injected into a Singleton
. But since there is no direct support for this, you will have to make this registration manually, by using the following extension method for instance:
public static IHttpClientBuilder AddTypedClientScoped<TClient>(
this IHttpClientBuilder builder)
where TClient : class
{
...
builder.Services.AddScoped<TClient>(s =>
{
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient(builder.Name);
var factory = s.GetRequiredService<ITypedHttpClientFactory<TClient>>();
return factory.CreateClient(httpClient);
});
return builder;
}
While starting an ASP.NET Core website while debugging, the framework will validate scopes for you, which causes an exception when the client is injected into a singleton.
But please be warned that the use this extension method doesn't detect all captive HttpClient
instances. That's because there are places in ASP.NET Core where components are registered as Transient
will still being kept alive for the duration of the application. One such case is hosted services. The AddHostedService
extension method is such example. Hosted services are registered as Transient
while being kept alive as Singleton
. In my optinion another design flaw. But that means that you should be careful while directly or indirectly injecting HttpClient
instances into hosted services.
What is the overhead of creating a new HttpClient per call in a WebAPI client?
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.
Which type of singleton pattern should be used for creating HTTP client for my web application
This doesn't sound like a good idea. In particular, take a peek into the docs of the HttpClient
class:
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
https://msdn.microsoft.com/en-us/library/system.net.http.httpclient%28v=vs.118%29.aspx
This means that accessing the very same singleton instance from multiple threads will lead to undefined issues.
What you could do however, is you could reuse the same instance across a single request. This can be done by storing an instance in the Items
container:
private static string ITEMSKEY = "____hclient";
public static HttpClient GetClient()
{
if ( HttpContext.Current.Items[ITEMSKEY] == null )
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(DemoConstants.DemoAPI);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
HttpContext.Current.Items.Add( ITEMSKEY, client );
}
return (HttpClient)HttpContext.Current.Items[ITEMSKEY];
}
Note, that since the HttpClient
implements IDisposable
, it still could be a good idea to dispose such instance somewhere in the pipeline, for example in the EndRequest
event of the application pipeline.
Update: as noted in a comment by @LukeH, the updated version of the docs for the .NET 4.5 and 4.6 states that some of methods of the HttpClient
class are thread safe:
https://msdn.microsoft.com/en-us/library/system.net.http.httpclient%28v=vs.110%29.aspx
The updated remarks section states that a single instance is basically a collection of shared settings applied to all requests executed by this instance. Then, the docs says:
In addition, every HttpClient instance uses its own connection pool, isolating its requests from requests executed by other HttpClient instances.
This means that the isolation of different pools could still make sense, my personal recommendation would still be then to not to have a singleton, as you possibly would still need to change some settings between consecutive requests.
HttpClient Singleton Implementation in ASP.NET MVC
Do you really want one instance?
I don't think you want one instance application-wide. You want one instance per thread. Otherwise you won't get very good performance! Also, this will resolve your questions #3 and #4, since no two threads will be accessing the same HttpClient at the same time.
You don't need a singleton
Just use Container.Resolve with the PerThreadLifetimeManager.
Related Topics
What's the Difference Between Utf8/Utf16 and Base64 in Terms of Encoding
Blocking Access to Private Member Variables? Force Use of Public Properties
Best Practice for Exception Handling in a Windows Forms Application
Calculating Hmacsha256 Using C# to Match Payment Provider Example
Assert an Exception Using Xunit
Run a Background Task from a Controller Action in ASP.NET Core
How to Make Backgroundworker Return an Object
Visual Studio Installer > How to Launch App at End of Installer
Error Message "Cs5001 Program Does Not Contain a Static 'Main' Method Suitable for an Entry Point"
Udp Hole Punching Implementation
Use Different Name for Serializing and Deserializing with JSON.Net
Installing Windows Service Programmatically
String Interpolation VS String.Format
How to Check If a Class Inherits Another Class Without Instantiating It