Benefits Of Using IHttpClientFactory in .NET Web Apps
Benefits of using IHttpClientFactory

Benefits Of Using IHttpClientFactory in .NET Web Apps

In last few articles, we have been looking at how HttpClient can be used in .NET applications.

We already know that creating new HttpClient instance for every request is not really recommended, as that might exhaust the resources on server. This might result in slow responses from backend HTTP APIs, or even timeouts. But if the HttpClient is singleton (or static), then it would not respect DNS changes.

In this article, let’s have a look at internals of DefaultHttpClientFactory implementation, and how does it try to solve two issues mentioned above.

Resource Exhaustion Issue

This DefaultHttpClientFactory the default implementation of IHttpClientFactory interface. The code for this can be found on GitHub.

If we look at the CreateClient implementation, below is the logic to create HttpClient instance:

  • It makes a call to CreateHandler.
  • CreateHandler is responsible for providing ActiveHandlerTrackingEntry instance. But the interesting thing is, these instances are managed as a Dictionary. Key in this dictionary is the name of HttpClient’s logical instance and value is ActiveHandlerTrackingEntry instance.
  • Note that CreateHandler calls GetOrAdd(TKey, Func<TKey,TValue>) method. It returns existing instance of ActiveHandlerTrackingEntry for the given key. If it is not present, then a new instance is created using Func delegate.

Once the handler to use is decided, it is passed for constructing HttpClient object. disposeHandler parameter is set to false, meaning these handlers are maintained in a pool inside IHttpClientFactory.

What does this mean ? This means that if CreateClient method is called multiple times to get a named client “github“, every time the HttpClient instance would be new. But the underlying handlers pipeline instances would be same, thus avoiding resource exhaustion. But HttpClient instance created for named client “googleMapsApi”, would be have different instances of http message handlers. This is applicable to not only named clients but also typed clients.

Code snippet from DefaultHttpClientFactory.  View Full Code at GitHub repo
Code snippet from DefaultHttpClientFactory. View Full Code at GitHub repo

So, it means that an ActiveHandlerTrackingEntry is created per named client. But how is this related to lifetime management of resources? Let’s first see what is inside an ActiveHandlerTrackingEntry.

Create Handler Entry

This is the method that is called to create a new ActiveHandlerTrackingEntry instance if it does not exist in the ConcurrentDictionary. This is very complicated method and high level overview of the logic is given below:

  • It creates a new scope object, so that scoped object instances can be managed properly
  • Next, it gets an instance of DefaultHttpMessageHandlerBuilder which will create an instance of HttpClientHandler. This HttpClientHandler is responsible for creating sockets connection and making the requests.
  • Next, IHttpMessageHandlerBuilderFilter  collection injected in DefaultHttpClientFactory is used to create additional HttpMessageHandlers.
  • Thus a whole pipeline of HttpMessageHandlers is created. This pipeline is then wrapped inside LifetimeTrackingHttpMessageHandler. This handler is just a marker handler which is used while cleaning up the resources.

if you want to know more details about internals, please refer this post from Andrew Lock.

Coming Back to Points

Ok, so coming back to original issues –

  • Resource Exhaustion due to many instances of HttpClient
  • Issues for respecting DNS modifications (stale DNS) in case we decide to use single instance of HttpClient.

Resource exhaustion issue is solved by pooling the pipelines of handlers. Same handler instances are used for two instances of HttpClient, as long as they are referring to a same named client.

DNS modifications might lead to stale data if single instance of HttpClient is used. Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Lifetime

By default, the http message handlers has default lifetime of 2 minutes. So, after every two minutes, the instances of http message hander pipeline is recycled.

Generally, this lifetime duration should be enough for most of the applications. If required, this lifetime duration can be changed via AddHttpClient middleware as shown in below code.

services.AddHttpClient<GitHubApionsumer>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "theCodeBlogger.com Demo");
}).SetHandlerLifetime(new TimeSpan(0, 10, 0)); // Handler Lifetime is set to 10 minutes
view raw Startup.cs hosted with ❤ by GitHub

I hope this information is useful. Let me know your thoughts.

Leave a Reply