docs icon indicating copy to clipboard operation
docs copied to clipboard

HttpClient is not always a good alternative

Open mlsomers opened this issue 1 year ago • 6 comments

HttpClient may be a great option for many scenarios but in a large multi-tenant application that communicates with many 3rd party API's it isn't really an option. I have tried to use it instead of WebRequest but ran into problems.

Using{ HttpClient } blocks caused our production servers to run out of ports quite fast. Turned out that it is not supposed to be disposed... But in that case it is not usable, we need to set the headers and have separate cookies for each and every customer connecting to different 3rd party API's in multiple threads.

I hope this is not an arm twister in order to promote micro service architecture with queues in stead of fast in-process synchronous communication. It's good for fallback when something goes down but immediate processing is much more user-friendly when possible.

If something gets deprecated there better first be a good replacement. Can't the flaws be fixed instead of deprecated?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

mlsomers avatar Jul 07 '22 19:07 mlsomers

Hi @mlsomers It sounds like you're seeing socket exhaustion issues.

As an alternative to using var client = new HttpClient();, which can lead to socket exhaustion, you should consider using the IHttpClientFactory.

The original and well-known HttpClient class can be easily used, but in some cases, it isn't being properly used by many developers.

Though this class implements IDisposable, declaring and instantiating it within a using statement is not preferred because when the HttpClient object gets disposed of, the underlying socket is not immediately released, which can lead to a socket exhaustion problem. For more information about this issue, see the blog post You're using HttpClient wrong and it's destabilizing your software.

Therefore, HttpClient is intended to be instantiated once and reused throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. That issue will result in SocketException errors. Possible approaches to solve that problem are based on the creation of the HttpClient object as singleton or static, as explained in this Microsoft article on HttpClient usage. This can be a good solution for short-lived console apps or similar, that run a few times a day.

IEvangelist avatar Jul 07 '22 19:07 IEvangelist

This newish article provides some further discussion about when and how to use HttpClient as well: https://docs.microsoft.com/en-us/dotnet/fundamentals/networking/httpclient-guidelines.

gewarren avatar Jul 07 '22 19:07 gewarren

Maybe I gave up on HttpClient to soon, but I'm not convinced yet. The advice of using a singleton will never work in my application, unless I use a lock(syncRoot) and bring our service down to a grinding halt (and make async await useless even if deadlocks can be avoided).

Re-using makes me nervous. I read about the typed client, but that's not good enough. There can be multiple instances of the same class sending messages for different customers that need different headers, cookies, HMAC API keys, etc for the same 3rd party service... How thread-safe can a shared HttpClient be?

mlsomers avatar Jul 07 '22 22:07 mlsomers

@karelz Could you help to answer Louis's questions?

gewarren avatar Jul 08 '22 17:07 gewarren

@mlsomers why would you need to lock your singleton? That would be necessary only if you plan to change shared state.

HttpClient is thread safe, unless you try to modify defaults while using it, etc. Once you have an instance, you can run parallel SendAsyncs as much as you want -- the right thing will happen. That means you need to set headers as part of each request.

If you want to have different defaults of headers for convenience, you can have multiple HttpClient (static) instances over the same HttpClientHandler. That is what IHttpClientFactory does under the hood.

karelz avatar Aug 01 '22 14:08 karelz

@karelz thank you for your reply. So I should use a static pool of HttpClients for a specific service (with default headers), and create HttpRequestMessage instances with all the additional unique headers for each thread/tenant...

There dos not seem to be a non DI (dependency injection) example but I guess I can figure that out...

The HttpMessageHandler, as far as I can see this item can be used in a using block and can be disposed like normal... However I see in the documentation that they are pooled which confuses me. What reason is there to pool HttpMessageHandler objects? Are they holding any resources other than memory? Actually I think I would not be using these explicitly on a server application very often, maybe these are more targeted for client applications that need to keep track of cookies from multiple hosts, eg. OpenIdConnect SSO clients for example, but then I still cannot find any reason why these would be pooled?

If you can shed some light on the thread-safty of HttpMessageHandler items (and if they are meant to be used in server-side applications at all), I guess this issue can be closed, even if it is going to be a painful rewrite and redesign of architecture. I hope the trusty WebRequest doesn't disappear too soon so we get the time to redesign stuff in a timely manner or else get stuck in an older framework.

mlsomers avatar Aug 02 '22 22:08 mlsomers

The HttpMessageHandler objects are tied to connections and are pooled to prevent socket exhaustion.

gewarren avatar Sep 26 '22 18:09 gewarren

I'm a huge advocate for this concept. When you come across articles with titles like "You're Using HttpClient Wrong," it leads me to believe that the issue lies in the design of the implementation itself, rather than user error. As someone who frequently creates multi-tenant applications, I completely get the rationale behind reusing sockets and the appeal of consolidating configurations in the startup file. While I don't think these features should be eliminated, there are countless situations where configurations may need to be adjusted on-the-fly. Ideally, I should be able to set an authentication header and utilize already open sockets for other tenants without having to worry about any interference or contamination.

Aeroverra avatar Apr 11 '23 21:04 Aeroverra