How to use HttpClient safely in ASP.NET MVC [duplicate] - c#

Given that the .net HttpClient has been designed with reuse in mind and is intended to be long lived and memory leaks have been reported in short lived instances. What guide lines are there where you want to make restful calls to a given endpoint using different bearer tokens (or any authorization header) when calling the endpoint for multiple users?
private void CallEndpoint(string resourceId, string bearerToken) {
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", bearerToken);
var response = await httpClient.GetAsync($"resource/{resourceid}");
}
Given the above code could be called by any number of threads on a web application it is easily possible that the header set in the first line is not the same one that is used when calling the resource.
Without causing contention using locks and maintaining a stateless web application what is the recommended approach to creating and disposing HttpClients for a single endpoint (My current practice is to create a single client per endpoint)?
Lifecycle
Although HttpClient does indirectly implement the IDisposable
interface, the recommended usage of HttpClient is not to dispose of it
after every request. The HttpClient object is intended to live for as
long as your application needs to make HTTP requests. Having an object
exist across multiple requests enables a place for setting
DefaultRequestHeaders and prevents you from having to respecify things
like CredentialCache and CookieContainer on every request, as was
necessary with HttpWebRequest.

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));

Related

.net HttpClient in C#: Exception when re-instantiating

Am querying multiple APIs with thousands of requests. Thus, I am looping over the end points and the requests. As it is suggested to re-use HttpClient instances, that's what I am doing. However, I need to set some parameters like timeouts, passwords etc. in the header for each API. Thus, the first API works perfectly, when trying to set the Parameters for the next API, it fails:
This instance has already started one or more requests. Properties can only be modified before sending the first request.
Generally I know that the properties need to be set before making any requests. So I considered resetting the HttpClient for each API and then just re-use it for the thousands of requests to that API. Surprisingly, I get the same error - and I have absolutely no idea why.
This is about what the code looks like:
private HttpClient ApiClient;
private List<Api> Endpoints;
[...]
foreach(Api api in this.Endpoints)
{
this.ApiClient = new HttpClient();
this.ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(api.mediaType));
this.ApiClient.Timeout = TimeSpan.FromMinutes(api.timeout);
this.ApiClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", api.credentials);
foreach (string url in api.urls)
{
# retrieve data from APIs and do something with it
}
}
As mentioned earlier, the first loop works perfectly fine. But when it starts over with the second api, I get a System.InvalidOperationException with the error message above when I try to set the ApiClient's timeout value.
Why so? I have created a brand new instance of HttpClient. Is there a better way to just reset the HttpClient?
The preferred way for generating HttpClients seems to be httpfactory: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1. Also note that reinstatiating httpclients as you are, even without your specific exception can lead to problems, as your code seems to be able to run into socket exhaustion as described in https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests.
You should have only one instance of HttpClient during the lifetime of your application.
So instead of creating a HttpClient and setting the DefaultRequestHeaders every time you loop over your endpoints use HttpRequestMessage and do the following:
this.ApiClient = new HttpClient();
foreach(Api api in this.Endpoints)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "url");
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("header value"));
var data = await ApiClient.SendAsync(request , HttpCompletionOption.ResponseContentRead);
}

Concurrency issue using HttpClient and HttpRequestMessage

My code looks something like this:
private readonly HttpClient _httpClient;
public SomeConstructor(HttpClient httpClient){
_httpClient = httpClient;
}
public void SomeMethod(string reqUrl, string payload){
var result = GetResponseStringAsync(reqUrl, payload).GetAwaiter().GetResult();
// do something with result
}
private async Task<string> GetResponseStringAsync(string reqUrl, string payload){
using (var req = new HttpRequestMessage("POST", reqUrl)){
using (var content = new StringContent(payload)){
// Attach content headers here (specific for each request)
req.Content = content;
// Attach request headers here (specific for each request)
// using req.Headers.TryAddWithoutValidation()
using (var response = await _httpClient.SendAsync(req))
{
return await response.Content.ReadAsStringAsync();
}
}
}
}
I need to send API requests that have different, signed headers per request, otherwise I will get back 401 (Unauthorized). That said, when I send a single request, I always got 200, indicating that the authorization headers are sent correctly. However, if I send multiple requests at once (say with concurrency level set to 10), only 1 request got 200 back, whereas the other 9 got 401s. If I click on these 9 links individually, however, I got 200s for every single one of them, as expected.
It seems to me that somehow, there's a concurrency issue that results in the proper headers not being attached to their corresponding requests, even when I create a new HttpRequestMessage for each request. HttpClient and HttpRequestMessage both are supposedly thread-safe but could someone provide an explanation as to why I'm still getting weird results when sending multiple requests at once?
Add:
I have something like this in my AppHost: Container.Register<ISomeConstructor>(x => new SomeConstructor(new HttpClient())); so I am sure I'm not accidentally modifying the
client anywhere else
Placing a lock around the HttpClient (just before the SendAsync call) makes it work and returns 200s 100% of the time, further convincing me that it's a concurrency issue
I'm deploying and running on Mono 6.8.0.105 -- could this be a Mono issue? I couldn't find any issues/bug reports on this though

HttpClient single instance with different authentication headers

Given that the .net HttpClient has been designed with reuse in mind and is intended to be long lived and memory leaks have been reported in short lived instances. What guide lines are there where you want to make restful calls to a given endpoint using different bearer tokens (or any authorization header) when calling the endpoint for multiple users?
private void CallEndpoint(string resourceId, string bearerToken) {
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", bearerToken);
var response = await httpClient.GetAsync($"resource/{resourceid}");
}
Given the above code could be called by any number of threads on a web application it is easily possible that the header set in the first line is not the same one that is used when calling the resource.
Without causing contention using locks and maintaining a stateless web application what is the recommended approach to creating and disposing HttpClients for a single endpoint (My current practice is to create a single client per endpoint)?
Lifecycle
Although HttpClient does indirectly implement the IDisposable
interface, the recommended usage of HttpClient is not to dispose of it
after every request. The HttpClient object is intended to live for as
long as your application needs to make HTTP requests. Having an object
exist across multiple requests enables a place for setting
DefaultRequestHeaders and prevents you from having to respecify things
like CredentialCache and CookieContainer on every request, as was
necessary with HttpWebRequest.
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));

C# How to pass on a cookie using a shared HttpClient

I have the following set up:
JS client -> Web Api -> Web Api
I need to send the auth cookie all the way down. My problem is sending it from one web api to another. Because of integration with an older system, that uses FormsAuthentication, I have to pass on the auth cookie.
For performance reasons I share a list of HttpClients (one for each web api) in the following dictionary:
private static ConcurrentDictionary<ApiIdentifier, HttpClient> _clients = new ConcurrentDictionary<ApiIdentifier, HttpClient>();
So given an identifier I can grab the corresponding HttpClient.
The following works, but I'm pretty sure this is bad code:
HttpClient client = _clients[identifier];
var callerRequest = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
string authCookieValue = GetAuthCookieValue(callerRequest);
if (authCookieValue != null)
{
client.DefaultRequestHeaders.Remove("Cookie");
client.DefaultRequestHeaders.Add("Cookie", ".ASPXAUTH=" + authCookieValue);
}
HttpResponseMessage response = await client.PutAsJsonAsync(methodName, dataToSend);
// Handle response...
Whats wrong about this is that 1) it seems wrong to manipulate DefaultRequestHeaders in a request and 2) potentially two simultanious requests may mess up the cookies, as the HttpClient is shared.
I've been searching for a while without finding a solution, as most having a matching problem instantiates the HttpClient for every request, hence being able to set the required headers, which I'm trying to avoid.
At one point I had get requests working using a HttpResponseMessage. Perhaps that can be of inspiration to a solution.
So my question is: is there a way to set cookies for a single request using a HttpClient, that will be safe from other clients using the same instance?
Instead of calling PutAsJsonAsync() you can use HttpRequestMessage and SendAsync():
Uri requestUri = ...;
HttpMethod method = HttpMethod.Get /*Put, Post, Delete, etc.*/;
var request = new HttpRequestMessage(method, requestUri);
request.Headers.TryAddWithoutValidation("Cookie", ".ASPXAUTH=" + authCookieValue);
request.Content = new StringContent(jsonDataToSend, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
UPDATE:
To make sure that your HTTP client does not store any cookies from a response you need to do this:
var httpClient = new HttpClient(new HttpClientHandler() { UseCookies = false; });
Otherwise you might get unexpected behavior by using one client and sharing other cookies.

Setting Accept Header without using MediaTypeWithQualityHeaderValue

In Asp.Net Web Api 2 what is the difference between setting an HttpClient Accept Header using the following traditional method :
HttpClient client = HttpClientFactory.Create(handler);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
and the following method :
var headers = new Dictionary<string, string>
{
{"Accept", "application/json"}};
headers.ForEach(h => client.DefaultRequestHeaders.Add(h.Key, h.Value));
Update 1:
Based on the answer by #DarrenMiller in the following post What is the overhead of creating a new HttpClient per call in a WebAPI client? it appears that the preferred method is using DefaultRequestHeaders property because it contains properties which are intended for multiple calls. Does this mean if I set a default header using a simple dictionary my HttpClient client will not be as efficient as the one which uses DefaultRequestHeaders? In addition I really cant understand how the values inside DefaultRequestHeaders will be reused? Lets say I create 20 HttpClient client using HttpClientFactory.Create and inside every single one of them I set DefaultRequestHeaders property [Do I really need to do it because DefaultRequestHeaders was meant to be reused?!]. Where does this reuse kick-in and does setting the DefaultRequestHeaders every time I create a HttpClient client result in some kind of performance hit?
Part one of your question: Is there any difference for adding headers?
HttpClient client = HttpClientFactory.Create(handler);
Method 1:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Method 2:
var headers = new Dictionary<string, string>{{"Accept", "application/json"}};
headers.ForEach(h => client.DefaultRequestHeaders.Add(h.Key, h.Value));
Method 1 gives you nice strongly typed values with the ability to add multiple accept types. Method 2 has one more "magic string" that could be a place for typos and there is no way to add multiple accept types.
Part 2 of your question: Where is the performance and reuse value?
The performance hit of using a new HttpClient for every request depends on your use case. Get a bench mark and measure to see if it matters. The performance on the developer is most likely where the gains will be. Consider that every HttpClient you use you have to remember a bunch of headers to add. If you forget to add a proper header, errors happen. So, you can use DefaultRequestHeaders to set these up in a factory.
public class ApiService
{
public static HttpClient GetClient()
{
var client = new HttpClient(new Uri("https://someservice/"));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//add any other setup items here.
return client;
}
}
Now use it:
public async Task DoStuff()
{
using(var client = ApiService.GetClient())
{
//client will have the proper base uri and all the headers set.
var data = await client.GetAsync<dynamic>("Sales");
//client will still have the proper base uri and all the headers set.
var data2 = await client.GetAsync<dynamic>("Products");
}
}
HttpClients should be short lived and always wrapped in a using statement. The reuse occurs when multiple requests are made using the same client.
UPDATE:
As others have mentioned, Microsoft recommends keeping one HttpClient for the life of the application to avoid excess overhead. The preferred way to get an HttpClient would be having a static one declared and then reference that as needed. Be aware that anything set in the default headers will go out with every request so make sure you don't put things there such as Authorization unless you're sure of the final destination of the request.

Categories

Resources