Recently, we were working on a project that needed numerous HTTP requests to be made. Initial implementation had a new HttpClient
object being created for every request being made. It looked to have some performance cost attached to it that led us to evaluate the effect of using single vs multiple instances of HttpClient
.
Problem Statement:
Whats the best way to use HttpClient
for multiple requests and the performance cost associated with it?
Assessment:
Went through the Microsoft documentation, which seemed updated based on last when I read few years back. Found a fineprint for myself that states:
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
This was a straight give away that we should use a single instance HttpClient
– irrespective of a usecase, one would want to keep distance from SocketException
errors (though probability of it would be high for heavy usage of HTTP requests).
Now, the query was how to have single HttpClient
for multiple requests but with different request payload for the calls? Also, does this has any impact on performance of the calls and if so, how much?
Resolution:
I started with looking into performance aspect for the two options. Created a test application that helped evaluate the time taken for various number of requests. Tried with www.google.com but seems they have some kind of check at 1000 requests so went ahead with www.bing.com that looked uniform till 5000 requests that I tried with.
for (var i = 0; i < noOfConnections; i++)
{
using (var httpClient = new HttpClient())
{
var result = httpClient.GetAsync(new Uri("http://www.bing.com/")).Result;
}
}
//having private static readonly HttpClient _httpClient = new HttpClient();
for (var i = 0; i < noOfConnections; i++)
{
var result = _httpClient.GetAsync(new Uri("http://www.bing.com/")).Result;
}
With the above, I got the following numbers on an average post few runs:
No of Requests | Multiple Instance (s) | Single Instance (s) | %age Diff |
100 | 20 | 16.67 | 16.65 |
500 | 103 | 88 | 14.56 |
1000 | 216 | 174 | 19.44 |
2000 | 430 | 351 | 18.37 |
5000 | 1032 | 906 | 12.21 |
It looked like the difference peaked around 1000 requests and overall there was an improvement with single instance.
Now, given we had a usecase where multiple HTTP requests has to be made simultaneously but with different payloads, looked at how to achieve it with single instance. Keeping multiple types of requests, unit testing, high load – One possible way looked like below that worked out well for us:
// Single instance of HttpClientManager was setup
public class HttpClientManager : IHttpClientManager
{
...
public HttpClientManager(HttpMessageHandler messageHandler)
{
_httpClient = new HttpClient(messageHandler);
}
private HttpRequestMessage SetupRequest(IRequestPayload requestPayload)
{
var request = new HttpRequestMessage
{
RequestUri = new Uri(requestPayload.Url)
};
switch (requestPayload.RequestType)
{
case RequestType.POST_ASYNC:
request.Method = HttpMethod.Post;
request.Content = GetHttpContent(requestPayload.ContentJson);
break;
case RequestType.PUT_ASYNC:
request.Method = HttpMethod.Put;
request.Content = GetHttpContent(requestPayload.ContentJson);
break;
case RequestType.DELETE_ASYNC:
request.Method = HttpMethod.Delete;
break;
case RequestType.GET_ASYNC:
request.Method = HttpMethod.Get;
break;
default:
request.Method = HttpMethod.Get;
break;
}
...
}
public HttpResponseMessage ExecuteRequest(IRequestPayload requestPayload)
{
HttpRequestMessage httpRequestMessage = SetupRequest(requestPayload);
HttpResponseMessage httpResponseMessage = _httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead).Result;
return httpResponseMessage;
}
private HttpContent GetHttpContent(string contentJson)
{
return new StringContent(contentJson, ENCODING, MEDIATYPE_JSON);
}
}
Since there are numerous articles on the web explaining details of the entire HttpClient
workflow and inner details, I will not cover that here but a quick explanation on couple of key info. In the code above:
HttpRequestMessage
is used to setup HttpClient object based on our need. We make use of the fact that HttpRequestMessage
can be used only once. After the request is sent, it is disposed immediately to ensure that any associated Content
object is disposed.
Making use of HttpClient
underlying implementation, have used HttpMessageHandler
more from the unit test point of view.
Conclusion:
One should use a single instance of HttpClient
at application level to avoid create/destroy of it multiple times. Further, results suggest this also has better performance with more than 12% improvement based on the load.
For multiple requests of different payloads, having a single instance HttpClient
but a new HttpRequestMessage
for every request looked a good approach to use.
P.S.: For .NET Core, Microsoft added a new interface around the same discussion to have better handle at HttpClient
instance: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
Entire code for lookup can be downloaded from here.