HttpClient single instance with different authentication headers
If your headers are usually going to be the same then you can set the DefaultRequestHeaders
. But you don't need to use that property to specify headers. As you've determined, that just wouldn't work if you're going to have multiple threads using the same client. Changes to the default headers made on one thread would impact requests sent on other threads.
Although you can set default headers on the client and apply them to each request, the headers are really properties of the request. So when the headers are specific to a request, you would just add them to the request.
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", bearerToken);
That means you can't use the simplified methods that don't involve creating an HttpRequest
. You'll need to use
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
documented here.
Some have found it helpful to use extension methods to isolate the code that updates the headers from the rest of a method.
Example of GET and POST methods done through an extension method that allow you to manipulate the request header and more of the HttpRequestMessage
before it is sent:
public static Task<HttpResponseMessage> GetAsync
(this HttpClient httpClient, string uri, Action<HttpRequestMessage> preAction)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
preAction(httpRequestMessage);
return httpClient.SendAsync(httpRequestMessage);
}
public static Task<HttpResponseMessage> PostAsJsonAsync<T>
(this HttpClient httpClient, string uri, T value, Action<HttpRequestMessage> preAction)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = new ObjectContent<T>
(value, new JsonMediaTypeFormatter(), (MediaTypeHeaderValue)null)
};
preAction(httpRequestMessage);
return httpClient.SendAsync(httpRequestMessage);
}
These could then be used like the following:
var response = await httpClient.GetAsync("token",
x => x.Headers.Authorization = new AuthenticationHeaderValue("basic", clientSecret));
Is setting the Authorization header in HttpClient safe?
With the approach you have, once you've set the default request header on your static instance, it will remain set without you having to keep setting it. This means that if you have multiple requests coming into your server, you could end up in a situation where the header is set for one user and then changed by another request before that first request makes it out the door.
One option to avoid this would be to use SendAsync
when using user-specific authorisation headers. This allows you to tie the header to a specific message, rather than setting it as a default for the HttpClient
itself.
The code is a bit more verbose, but would look something like this:
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://path/to/wherever"))
{
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "TheToken");
using (var httpResponseMessage = httpClient.SendAsync(httpRequestMessage))
{
// ...
}
}
As you can see, the header is set specially on each request and therefore the issue of mixing up the headers goes away. The obvious downside is that this syntax is more verbose.
Is it fine to use one HttpClient instance for each host my application needs to talk to?
I know that, when using the Microsoft dependency injection container, the best practice to handle HttpClient instances is using the IHttpClientFactory interface provided by the Microsoft.Extensions.Http nuget package.
Correct.
Unfortunately the classes implementing the IHttpClientFactory interface are not public (as you can verify here), so the only way to exploit this pattern is using the Microsoft dependency injection container (at least it's the only one that I know). Sometimes I need to maintain old applications using a different container, so I need to figure out a best practice even when the IHttpClientFactory approach cannot be used.
Microsoft.Extensions.DependencyInjection
("MEDI") should be thought of a (simplistic) abstraction over multiple DI systems - it just so happens to come with its own basic DI container. You can use MEDI as a front for Unity, SimpleInject, Ninject, and others.
As explained in this famous article and confirmed in the Microsoft docs too the
HttpClient
class is designed to be instantiated once per application lifetime and reused across multiple HTTP calls.
Not exactly.
- You don't want a singleton
HttpClient
used by all consumers ofHttpClient
in your application because different consumers might have different assumptions about (as you later point out)DefaultRequestHeaders
and otherHttpClient
state. Some code may also assume thatHttpClient
is not using anyDelegatingHandler
instances either. - You also don't want any instances of
HttpClient
(created using its own parameterless constructor) with an unlimited lifetime because of how its default internalHttpClientHandler
handles (or rather, doesn't handle) DNS changes. Hence why the defaultIHttpClientFactory
imposes a lifetime limit of 2 minutes for eachHttpClientHandler
instance.
This opens a question: what happens if I have a singleton HttpClient instance and somewhere in my code I use the property DefaultRequestHeaders to set some common HTTP request headers useful to call one of the host my application needs to communicate with?
What happens? What happens is what you can expect: different consumers of the same HttpClient
instance acting on wrong information - such as sending the wrong Authorization
header to the wrong BaseAddress
. This is why HttpClient
instances should not be shared.
This is potentially dangerous, because different hosts could require different values for the same request header (think of authentication as an example of that). Furthermore, modifying DefaultRequestHeaders concurrently from two threads could potentially mess up the internal state of the HttpClient instance, because of the lack of thread safety guarantees.
This isn't necessarily a "Thread safety" issue - you can have a single-threaded application that abuses a singleton HttpClient
this way and still have the same issue. The real issue is that different objects (the consumers of HttpClient
) are assuming that they are the owner of the HttpClient
when they aren't.
Unfortunately C# and .NET do not have a built-in way to declare and assert ownership or object lifetimes (hence why IDisposable
is a bit of a mess today) - so we need to resort to different alternatives.
create one instace of HttpClient for each host the application needs to communicate with. Every call to one specific host will then use the same instance of HttpClient. Concurrent calls to the same host are safe, because of the documented thread safety of methods used to perform calls.
(By "host" I assume you mean HTTP "origin"). This is naive and won't work if you make different requests to the same service with different access-tokens (if the access-tokens are stored in DefaultRequestHeaders
).
create one service for each host the application needs to communicate with. The HttpClient instance is injected inside this service and the service itself is used as a singleton in the application. This service is used to abstract away the access to the host it is coupled with. Classes like this are fully testable as illustrated here.
Again, don't think of HTTP services in terms of "hosts" - otherwise this has the same problem as above.
the only point where instances of HttpClient are created and configured is the composition root of the application. The code in the composition root is single threaded, so it is safe to use properties like DefaultRequestHeaders to configure the HttpClient instances.
I'm not sure how this helps either. Your consumers might be stateful.
Anyway, the real solution, imo, is to implement your own IHttpClientFactory
(it can also be your own interface!). To simplify things, your consumers' constructors won't accept a HttpClient
instance, but instead accept the IHttpClientFactory
and call its CreateClient
method in order to get their own privately-owned and stateful instance of HttpClient
which then uses the pool of shared and stateless HttpClientHandler
instances.
Using this approach:
- Each consumer gets its own private instance of
HttpClient
that they can alter as they like - no worries about objects modifying instances that they don't own. Each consumer's
HttpClient
instance does not need to be disposed - you can safely disregard the fact they implementIDisposable
.- Without pooled handlers, each
HttpClient
instance owns its own handler, which must be disposed. - But with pooled handlers, as with this approach, the pool manages handler lifetime and clean-up, not the
HttpClient
instances. - Your code can call
HttpClient.Dispose()
if it really wants to (or you just want to make FxCop shut-up) but it wont do anything: the underlyingHttpMessageHandler
(PooledHttpClientHandler
) has a NOOP dispose method.
- Without pooled handlers, each
Managing the lifetime of
HttpClient
is irrelevant because eachHttpClient
only owns its own mutable state likeDefaultRequestHeaders
andBaseAddress
- so you can have transient, scoped, long-life'd or singletonHttpClient
instances and it's okay because they all dip into the pool ofHttpClientHandler
instances only when they actually send a request.
Like so:
/// <summary>This service should be registered as a singleton, or otherwise have an unbounded lifetime.</summary>
public QuickAndDirtyHttpClientFactory : IHttpClientFactory // `IHttpClientFactory ` can be your own interface. You do NOT need to use `Microsoft.Extensions.Http`.
{
private readonly HttpClientHandlerPool pool = new HttpClientHandlerPool();
public HttpClient CreateClient( String name )
{
PooledHttpClientHandler pooledHandler = new PooledHttpClientHandler( name, this.pool );
return new HttpClient( pooledHandler );
}
// Alternative, which allows consumers to set up their own DelegatingHandler chains without needing to configure them during DI setup.
public HttpClient CreateClient( String name, Func<HttpMessageHandler, DelegatingHandler> createHandlerChain )
{
PooledHttpClientHandler pooledHandler = new PooledHttpClientHandler( name, this.pool );
DelegatingHandler chain = createHandlerChain( pooledHandler );
return new HttpClient( chain );
}
}
internal class HttpClientHandlerPool
{
public HttpClientHandler BorrowHandler( String name )
{
// Implementing this is an exercise for the reader.
// Alternatively, I'm available as a consultant for a very high hourly rate :D
}
public void ReleaseHandler( String name, HttpClientHandler handler )
{
// Implementing this is an exercise for the reader.
}
}
internal class PooledHttpClientHandler : HttpMessageHandler
{
private readonly String name;
private readonly HttpClientHandlerPool pool;
public PooledHttpClientHandler( String name, HttpClientHandlerPool pool )
{
this.name = name;
this.pool = pool ?? throw new ArgumentNullException(nameof(pool));
}
protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken )
{
HttpClientHandler handler = this.pool.BorrowHandler( this.name );
try
{
return await handler.SendAsync( request, cancellationToken ).ConfigureAwait(false);
}
finally
{
this.pool.ReleaseHandler( this.name, handler );
}
}
// Don't override `Dispose(Bool)` - don't need to.
}
Then each consuimer can use it like so:
public class Turboencabulator : IEncabulator
{
private readonly HttpClient httpClient;
public Turboencabulator( IHttpClientFactory hcf )
{
this.httpClient = hcf.CreateClient();
this.httpClient.DefaultRequestHeaders.Add( "Authorization", "my-secret-bearer-token" );
this.httpClient.BaseAddress = "https://api1.example.com";
}
public async InverseReactiveCurrent( UnilateralPhaseDetractor upd )
{
await this.httpClient.GetAsync( etc )
}
}
public class SecretelyDivertDataToTheNsaEncabulator : IEncabulator
{
private readonly HttpClient httpClientReal;
private readonly HttpClient httpClientNsa;
public SecretNsaClientService( IHttpClientFactory hcf )
{
this.httpClientReal = hcf.CreateClient();
this.httpClientReal.DefaultRequestHeaders.Add( "Authorization", "a-different-secret-bearer-token" );
this.httpClientReal.BaseAddress = "https://api1.example.com";
this.httpClientNsa = hcf.CreateClient();
this.httpClientNsa.DefaultRequestHeaders.Add( "Authorization", "TODO: it's on a postit note on my desk viewable from outside the building" );
this.httpClientNsa.BaseAddress = "https://totallylegit.nsa.gov";
}
public async InverseReactiveCurrent( UnilateralPhaseDetractor upd )
{
await this.httpClientNsa.GetAsync( etc )
await this.httpClientReal.GetAsync( etc )
}
}
Related Topics
Run the Current Application as Single Instance and Show the Previous Instance
How to Programmatically Limit My Program's CPU Usage to Below 70%
Intersect with a Custom Iequalitycomparer Using Linq
Best Way to Switch Behavior Based on Type
Including Pictures in an Outlook Email
In-Memory Database Doesn't Save Data
Blocking Access to Private Member Variables? Force Use of Public Properties
Ms Chart Rectangular Annotation Width in Percent and Not Pixel
Find Image Format Using Bitmap Object in C#
How to Derive Xml Element Name from an Attribute Value of a Class Using Annotations
Use Different Name for Serializing and Deserializing with JSON.Net
How to Disable Alt + F4 Closing Form
How to Get the First Digit in an Int (C#)
How to Access Elements of a Jarray (Or Iterate Over Them)