Dai
Dai

Reputation: 155558

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:

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

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:

My questions:

Upvotes: 4

Views: 1117

Answers (1)

smurtagh
smurtagh

Reputation: 1187

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.

Upvotes: 2

Related Questions