Why to Use IHttpClientFactory to Make HTTP Requests in .NET 5.0 or .NET Core

In any system nowadays, applications or services need to communicate with each other to achieve the target process. This communication can occur in many different forms or types, but one of these types is the direct communication via HTTP request.

An ASP.NET Core application or service can communicate with external APIs or services via HTTP request as we stated, and this can be achieved by creating an instance of the famous HttpClient class. However, the HttpClient class acts as a session to send the HTTP requests with a collection of predefined settings that are applied to all requests being generated from the created instance.

For a while, anyone reading this can say “Why we are tackling this straightforward topic? Isn’t it a simple thing to create an instance of HttpClient class and generate a request?”. Well, I think we all thought the same at the beginning when we started using HttpClient, but we ended up digging deep into this topic when we faced a lot of problems under large load, where a lot of surprises were hidden underneath. That said, let us start figuring out and spend some time to understand properly how HttpClient class works and how to use it in a correct manner without running into issues within your applications or services.

HTTPClient in .NET 5.0 (.NET Core) – Common Issues

I will start this section by listing out the common issues that are very well know in using the HttpClient class and after that we go over each one to figure out what does it mean. The few issues associated with using HttpClient in the old way are known as the below:

  • Resource Exhaustion Problem or what is known sometimes as Socket Exhaustion
  • Stale DNS problems (DNS Changes Not Reflecting)

Resource Exhaustion Problem

To understand these common issues or problems and to know why to adopt IHttpClientFactory in our applications to call external applications or services, we need to unveil how these issues can popup by troubleshooting a sample request on your local machine.

A very simple code sample shown below from a console app using .NET 5.0 is used to call the publicly known Open Movie Database API (http://www.omdbapi.com/) to obtain movie information for a certain search query (providing your subscription API key). The code below instantiates a new instance of HttpClient, creates a Get request to an external API resource, wait for the response, and returns the response as a JSON after reading the content as a string.

var apiKey = "<YOUR API KEY>";
var movieQuery = "Guardians";
using var httpClient = new HttpClient();
string omdbApiUrl = $"http://www.omdbapi.com?apikey={apiKey}&s={movieQuery}";
var response = await httpClient.GetAsync(omdbApiUrl);
Console.WriteLine(await response.Content.ReadAsStringAsync());

This looks very simple, basic, and works fine right! But wait, did you try at any time to troubleshoot such a simple code for using HttpClient and see what is going underneath? Let us take some time and run this piece of code to see what is happening behind the scenes every time you execute the command line.

To see what is happening on the network level and get some statistics, we will use in windows a very well-known command called netstat which displays active TCP connections, ports on which the computer is listening, Ethernet statistics, the IP routing table etc… If you use this command without any parameters it will list all active connections on your pc, that is why we will narrow the results by filtering it down with the connections to the OMDB API. You can get the IP address of the OMDB API host by ping command.

Running ping omdbapi.com returns the IP address we want 172.67.19.55

Now, run the console app just once and try the following command to get network statistics results: netstat -ano | findstr 172.67.19.55

Well for now everything looks great and nothing unusual. Now, try running again the console app several times. In the first batch, run it twice simultaneously. While in the second batch, run the app three times. After that, we want to check again the network statistics and see what we got in the below figures.

As you can see, each time you execute the code a request is created to the API endpoint which opens a new connection to the external API. In the images above, you can see more network connections are established when running the netstat command after making requests to our API endpoint. Moreover, you think that disposing the HttpClient connection will free up the reserved network resources, but this is not the case where each time the code executes it leaves the network connections in a TIME_WAIT state. So, what is the TIME_WAIT state?

By referring to the netstat man page, we can see that the TIME_WAIT is described as the below:

We can conclude from the TIME_WAIT states that the local endpoint (this side at our machine) has closed the connection, but the connection is kept “alive” or on hold so that any delayed packets arriving could be matched to the established connection and handshake properly. That said, the connections will be completely removed when they time out within the default time of the system. RFC 793 specifies MSL as 2 minutes and the Windows system default value is 2 minutes, but it can be adjusted using the TcpTimedWaitDelay registry setting (refer to old documentation version).

For more information about the TIME_WAIT and its design implications, you can refer to this post where we take from it the below useful statement:

The reason that TIME_WAIT can affect system scalability is that one socket in a TCP connection that is shut down cleanly will stay in the TIME_WAIT state for around 4 minutes. If many connections are being opened and closed quickly then socket’s in TIME_WAIT may begin to accumulate on a system; you can view sockets in TIME_WAIT using netstat. There are a finite number of socket connections that can be established at one time and one of the things that limits this number is the number of available local ports. If too many sockets are in TIME_WAIT you will find it difficult to establish new outbound connections due to there being a lack of local ports that can be used for the new connections.

Stale DNS Problem

Now, we have a clear image about what so called Socket Exhaustion, but what about Stale DNS (Domain Name System) problems?

You may think that the solution for the first problem is creating a Singleton or Static HTTPClient object and this should help to resolve this issue. Consider the below example for more illustration.

private static HttpClient httpClient = new HttpClient();
static async Task Main(string[] args)
{
var apiKey = "<YOUR API KEY>";
var movieQuery = "Guardians";
string omdbApiUrl = $"http://www.omdbapi.com?apikey={apiKey}&s={movieQuery}";
var response = await httpClient.GetAsync(omdbApiUrl);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}

The code above creates a new instance of HttpClient and not dispose it during the whole application lifetime. This means that the HttpClient instance is reused in a way only one connection is maintained. You may think that this works very fine, but this is an additional problem that was found in the .NET Core framework, where the Singleton or Static HTTPClient object does not respect the DNS update or change. What does this mean? This means that if there are no DNS or network-level changes to the external API’s connection, then the provided code above will work fine. Otherwise, if this happens you will have to restart the app pool of the application or the API hosted under IIS, or the application itself (in our case console app) or create a new instance of HttpClient. You can have more information about this topic and issues using the HttpClient class directly in the official Microsoft documentation.

To recap about the presented issues above, we can have the below summary:

  • Each new HTTPClient object creates a new socket instance
  • Creating new instance of HTTPClient objects for each request might exhaust the number of sockets available
  • HTTPClient object does not release the sockets immediately even if it is called within “using” (IDisposable) block
  • HttpClient misuse leads to Socket exceptions
  • Singleton or Static HTTPClient object does not respect the DNS update or change in the .NET core

As per Microsoft documentation, we must respect the below guideline

HttpClient is intended to be instantiated once and re-used throughout the life of an application. Especially in server applications, creating a new HttpClient instance for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.

But what is the solution for the above problems and how we can use the HttpClient efficiently in a way to avoid falling in such a trap? The answer is simple and provided by Microsoft in .NET 5.0 or .NET Core itself (starting version 2.1). The key solution is “IHttpClientFactory” or “HttpClientFactory

Using IHttpClientFactory to Create HTTPClient Request

We will start by defining the IHttpClientFactory based on Microsoft documentation

A factory abstraction for a component that can create HttpClient instances with custom configuration for a given logical name

Now that we introduced IHttpClientFactory and familiar with the issues discussed previously, we will try to fix them by injecting the IHttpClientFactory within our application and create a new HttpClient instance from it.

private readonly IHttpClientFactory httpClientFactory;
public MoviesController(IHttpClientFactory httpClientFactory)
{
	httpClientFactory = httpClientFactory;
}
 
[HttpGet]
public async Task<string> Get(string movieQuery)
{ 
var apiKey = "<YOUR API KEY>";
var httpClient = httpClientFactory.CreateClient();
string omdbApiUrl = $"http://www.omdbapi.com?apikey={apiKey}&s={movieQuery}";
// ... continue with same code here
}

We defined a read only variable of IHttpClientFactory in the controller so that we can use Dependency Injection to get the injected IHttpClientFactory within our service. To enable Dependency Injection of the IHttpClientFactory instance, we need to make sure that we call services.AddHttpClient() method in ConfigureServices method of Startup.cs of our service or API.

Run the code provided above and do the same exercise by checking the netstat statistics of the network, you should have the below similar results.

The above illustration is a clear evidence about the efficiency of using the IHttpClientFactory in instantiating the HttpClient, where we no more have TIME_WAIT states and only one connection is established with one port or socket being reserved. Hence, the benefits of using the IHttpClientFactory are many and we can mention some of them to highlight several ideas (refer to Microsoft documentation to see all of these benefits).

  • Centralized configuration and instantiation of HttpClient objects
  • Manages the pooling and lifetime of HttpMessageHandler instances
  • HttpMessageHandler pooling solves the Resource exhaustion problems
  • Cycling HttpMessageHandler instances at regular intervals solves Stale DNS problems
  • Enable Authentication per request basis if needed

How to use IHTTPClientFactory

There are different ways by which we can use IHttpClientFactory in our applications or services. These are usually known as consumption patterns, and they are as follows:

  • Basic HTTPClient
  • Named HTTPClient
  • Typed HTTPClient
  • Generated Clients

All the above techniques leverage the IHttpClientFactory interface to Dependency Injection required client object. We will not go into details about these patterns and repeat what is already addressed and discussed very well with examples in Microsoft Documentation, so refer to this documentation to learn more about these patterns.

IHttpClientFactory is a pain relief, where it solved for us all the issues and problems, we addressed at the beginning of this post that we saw with instantiating the HttpClient instance directly. With the help of the different consumption patterns and approaches stated in Microsoft Documentation, we now have the ability to write a well separated and easier to maintain loosely coupled code through Dependency Injection.

Were you using the HttpClient in the correct manner before this post? If no, then now you are!

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Website Powered by WordPress.com.

Up ↑

%d bloggers like this: