Using HttpClient as it was intended (because you’re not)

sink

 

Async programming has become ubiquitous and the standard tool for making async HTTP requests with C# is HttpClient from the System.Net.Http namespace. Examples are aplenty, but good examples are few and far between. Because HttpClient implements IDisposable we are conditioned to new it up in a using statement, make the call and get out as fast as possible.

This is WRONG.

This blog post will pull together advice from the few good resources online that touch on various aspects of why you are using it wrong, and how to use it correctly.

HttpClient is designed for re-use. You should use a single instance of it throughout the lifetime of your application, or at least as few as possible when request signatures will vary (explained later). The main reason for this is that each instance of HttpClient will open a new socket connection and on  high traffic sites you can exhaust the available pool and receive a System.Net.Sockets.SocketException.

Additionally, by re-using instances you can avoid the not-insignificant overhead of establishing new TCP connections because HttpClient can re-use its existing connections, and do so in a thread safe manner.

So, if : using(var client = new HttpClient())
is wrong, what do we do?

You create a single static instance of HttpClient. In console or desktop applications you can expose this instance just about anywhere. In an Asp.Net Web API application, you can create it in the Global.asax.cs file.

Recipe: Your web api always uses the same header values and the same base URL

Global.asax.cs:

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
 
namespace HttpClientGuidance
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        internal static HttpClient httpClientInstance;
 
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
 
            Uri baseUri = new Uri("https://someresource.com");
 
            httpClientInstance = new HttpClient();
            httpClientInstance.BaseAddress = baseUri;
            httpClientInstance.DefaultRequestHeaders.Clear();
            httpClientInstance.DefaultRequestHeaders.ConnectionClose = false;
            httpClientInstance.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
 
            ServicePointManager.FindServicePoint(baseUri).ConnectionLeaseTimeout = 60 * 1000;
        }
    }
}

Here we have set up the client one time. All uses of it will :

  1. Use the same base address. You can tack on varying routes (e.g. /api/somecontroller, /api/someothercontroller ) without issue.
  2. Set the request content-type to application/json
  3. Will attempt to keep the connection open (.ConnectionClose = false) which makes more efficient use of the client.

Since keeping connections open can prevent load balancing, we compensate by setting a connection lease timeout on the base URL through the ServicePointManager. This ensures connections are used efficiently but not indefinitely.

The ConnectionLeaseTimeout has an additional bonus. Since HttpClient will cache DNS data, you could run into a situation when a DNS change breaks your code because an instance of HttpClient is still using DNS from before the change. This can happen particularly when using a Blue/Green deployment strategy where you deploy an update of your app to production but don’t make it live until flipping DNS – and your base URI depends on those DNS settings. Setting the timeout will minimize downtime.

Basic Usage Example:

using HttpClientGuidance.Models;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
 
namespace HttpClientGuidance.Controllers
{
    public class BasicClientUsageController : ApiController
    {
        public async Task<HttpResponseMessage> Post()
        {
            Fruit grape = new Fruit { FruitName = "Grape", FruitColor = "Purple" };
            return await WebApiApplication.httpClientInstance
                .PostAsJsonAsync("/api/somecontroller", grape);
        }
    }
}
RECIPE: YOUR WEB API Uses a few different configurations

While HttpClient is thread safe, not all of its properties are. You can cause some very difficult to identify bugs by re-using the same instance but changing the URL and/or headers before each call. If this configuration will vary then you cannot use the first recipe.

If you have a manageable number of configurations, create an HttpClient instance for each one. You will be slightly less efficient than an single instance, but significantly more efficient than creating and disposing each time.

//complete Global.asax.cs elided
internal static HttpClient httpClientInstance;
internal static HttpClient twitterClient;
internal static HttpClient bitcoinExchngClient;

In this way you can re-use the client instances as appropriate. Just set all the headers, base URLs, and ServicePointManager connection timeouts individually.

RECIPE: You Have many different API’s to call and maybe you even often add new ones with each software release. (or you just like this method best)

In this case it can become unmanageable to create separate client instances for all of the HTTP calls you make. You can still gain all the advantages of a single client instance by varying your HttpRequestMessage instead of your HttpClient.

Observe our new Global.asax.cs file:

using System.Net;
using System.Net.Http;
using System.Web.Http;
 
namespace HttpClientGuidance
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        internal static HttpClient httpClientInstance;
 
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);            
 
            httpClientInstance = new HttpClient();
            httpClientInstance.DefaultRequestHeaders.ConnectionClose = false;
            
            ServicePointManager.FindServicePoint("some uri").ConnectionLeaseTimeout = 60 * 1000;
            ServicePointManager.FindServicePoint("some other uri").ConnectionLeaseTimeout = 60 * 1000;
            ServicePointManager.FindServicePoint("some other other uri").ConnectionLeaseTimeout = 60 * 1000;
            //etc.....
        }
    }
}

You can see we have eliminated the content type and the base URI because that is going to change often in our use of the client.

Also we have configured the ConnectionLeaseTimeout for all the URI’s that we know we will be using. If you don’t know at design time all the URI’s then it is okay to do this at runtime – at least it is better to do it at runtime than not at all.

To make use of this single client instance you will create an HttpRequestMessage with all the configuration that you would have set directly on the HttpClient if it were always going to be the same. Then you make the request through the client’s SendAsync method.

Since it is so common in ASP.Net Web API to send object instances as request content I am demonstrating the use of ObjectContent but any of the derivatives of HttpContent can be used, StringContent also being very common.

using HttpClientGuidance.Models;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web.Http;
 
namespace HttpClientGuidance.Controllers
{
    public class BasicClientUsageController : ApiController
    {
        public async Task<HttpResponseMessage> Post()
        {
            Fruit grape = new Fruit { FruitName = "Grape", FruitColor = "Purple" };
 
            var msg = new HttpRequestMessage(HttpMethod.Post, "http://someurl");
            msg.Content = new ObjectContent<Fruit>
(grape, new JsonMediaTypeFormatter(), (MediaTypeHeaderValue)null);
            msg.Headers.Authorization = new AuthenticationHeaderValue("whatever scheme", "whatever value");
            msg.Headers.Add("some custom hdr", "some value");
 
            return await WebApiApplication.httpClientInstance.SendAsync(msg);
        }
    }
}

While this clearly does not demonstrate all of the options on HttpRequestMessage, the point is that all of the configuration can be done on the message without affecting the efficiency of using a single instance of the client. So you can feel free to create as many varying request messages as you need.

Summing up

I hope this sheds some light on a topic that is under-documented and yet highly-relevant to .Net developers that are using API’s (is anyone not?)

Despite all you have seen and been taught, do not dispose your HttpClient instance(s). Okay, if your app is some utility app that would never come close to exhausting connections then it doesn’t really matter as long as you know how to do it right when the time comes. But generally, re-use it, don’t abuse it.

Special thanks to the following resources that I used to pull this together:

3 thoughts on “Using HttpClient as it was intended (because you’re not)

  1. Michael LaRosa July 5, 2017 / 2:13 pm

    In your first “recipe”, I don’t understand why you call Clear(), since you have just created the HttpClient instance. Is there a default header that you are specifically interested in eliminating?

    Like

    • Bob Crowley July 5, 2017 / 6:38 pm

      Every example I’ve ever seen clears the headers first so I always assumed (without checking) that some kind of default headers were applied. Now having tried it, I don’t see any headers at all so it is probably unnecessary. Thanks for the input.

      Like

  2. Vladimir Pecanac August 3, 2017 / 1:10 pm

    Hi Bob,
    Thanks for nicely summarizing the HttpClient instances problem. One can too easily slip up while using HttpClient the wrong way.
    I wish this post came out before I’ve written mine, it would have saved me a few hours of research and compilation of different source material. 🙂

    Keep up the good work!

    Like

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 )

Google+ photo

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

Connecting to %s