Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IHttpClientFactory, SetBearerToken(), AddHttpClient<>, and "You're using HttpClient wrong"

I'm sure we've all read this article that made waves back in 2016: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

For those who haven't, in summary (emphasis mine):

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.

Now, consider an ASP.NET Core application that consumes other web-services by using a HttpClient. Its usage of HttpClient falls into two general situations:

  1. An outgoing request that is unauthenticated or uses the website's own credentials - in which case a single HttpClient really can be shared by all parts of the program.

  2. An outgoing request made on behalf of one of the website's current visitors - or a request that's otherwise using request-specific credentials.

    • While a single HttpClient instance can be used, you must be careful not to mutate its state, for example, by setting DefaultRequestHeaders (e.g. by using the SetBearerToken extension method).

Practically all of the guidance for using HttpClient in ASP.NET Core says to:

  • Use Typed Clients (POCOs that accept a HttpClient instance as a constructor parameter, and any other DI services).
  • Register these Typed Clients using services.AddHttpClient<TClient>().
    • This will register TClient as a transient instance.
    • This will register ITypedHttpClientFactory<TClient> as a transient instance.
    • This will register IHttpClientFactory as a singleton
    • This will register IHttpMessageHandlerFactory as a singleton

With that now discussed, I'll bring your attention towards a contradiction:

  • The famous blog article - and now Microsoft's own documentation - says the HttpClient instances must be long-life'd.
  • ASP.NET Core's HttpClient DI system really makes sure that HttpClient instances are short-life'd.

However, might this be okay if the blog article should really be talking about the underlying HttpMessageHandler (which is the real HttpClient implementation which is simply wrapped by the thin shell class HttpClient) - instead of the outer class HttpClient?

Confounding things, people report problems with using long-life'd HttpClient instances too and introduce other workarounds which all involve the static class ServicePointManager which makes me uncomfortable:

  • https://itnext.io/reusing-httpclient-didnt-solve-all-my-problems-142a32a5b4d8
    • The author suggests tweaking ServicePointManager during application startup.
    • ...including disabling Nagle's algorithm - which I think is probably a very bad idea.
  • http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html
    • The author suggests using ConnectionLeaseTimeout
    • ...but you need to call ServicePointManager.FindServicePoint() for every URI you call! That doesn't seem right to me.

My questions:

  • Are HttpClient instances really meant to be long-life'd or short-life'd?
  • Or is it just the HttpMessageHandler instances that need to be long-life'd?
  • If HttpClient or HttpMessageHandler instances are meant to be long-life'd:
    • How do I correctly address the ServicePointManager issues brought up in both linked blog posts?
    • Do I absolutely have to avoid using HttpClient.DefaultRequestHeaders and instead explicitly set the Authorization (Cookies or Credentials) headers on each HttpRequestMessage? Is there another way which has less hassle?
    • Could I add those headers using my own DelegatingHandler? What should be the DI registration of this proposed handler then?
  • If HttpClient instance (but not HttpMessageHandler instances) are meant to be short-life'd:

    • ...then it's okay to use HttpClient.DefaultRequestHeaders (and SetBearerToken())?
    • What is the lifetime policy of HttpMessageHandler instances then?
    • Can I still use HttpClient.DefaultRequestHeaders (and SetBearerToken())?
  • If HttpClient instance and HttpMessageHandler instances are meant to be short-life'd:

    • What about the issues mentioned in the original 2016 blog posting?
      • Does IHttpClientFactory do anything to mitigate those problems?
like image 992
Dai Avatar asked Oct 25 '25 05:10

Dai


1 Answers

The underlying problem is with socket connections. new HttpClient() makes a new port for every request and leaves them open in the TIME_WAIT status: enter image description here

This isn't the right state for them to sit in, and it takes up a socket without being reused. When the server runs out of sockets, .net throws a SocketException because it can't make a connection without an open socket. This is also called port exhaustion because the port runs out of sockets.

IHttpClientFactory keeps ports in the ESTABLISHED status, reuses them if another request comes in, and if no request comes in for 4 minutes, it closes them. enter image description here

Your questions are all ultimately about HTTP and socket connections, but you're using .net classes to describe it. IHttpClientFactory manages the tcp socket connections with a pool of HttpClientHandlers so you don't have to. The answer to the rest of your questions are here: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

Handlers will be reused by the factory when it needs to create new HttpClients. You can still set dynamic values like the bearer token through your own implementation, e.g. write your own Get<T> method that takes in a string bearerToken and then sets the Authorization header so you can keep that code in one place.

... I'll bring your attention towards a contradiction:

The famous blog article - and now Microsoft's own documentation - says the HttpClient instances must be long-life'd.
ASP.NET Core's HttpClient DI system really makes sure that HttpClient instances are short-life'd.

Where do you see Microsoft's documentation about IHttpClientFactory that says httpclients need to live long? Through the factory, they're scoped in DI, which means they're disposed at the end of the request. That's why it's safe to reuse the handler, but modify the client at runtime.

like image 135
smurtagh Avatar answered Oct 26 '25 23:10

smurtagh