ChatGPT解决这个技术问题 Extra ChatGPT

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.

Are you talking about a relatively small number of different auth headers or lots, such as unique for every user?
@ToddMenier - It would be a unique header for each user. It would be that users oauth token. I think scott hannen has put me on the right track. Looks like some extension methods will be in order.
Hello @Bronumski , can you share the way you solved this? Im having the same issue with multiple threads adding the same header but with different content.
@LuisMejia - I have updated scotts answer with an examples of how I did the GET and POST. The same principle is used on any of the other methods you want to implement. The extension method includes an action that allows you to manipulate the HttpRequest before it is sent.
@Bronumski Thanks for the answer... seems like Im going a similar way making use of sendasync and passing a request as parameter with the custom headers.

S
Scott Hannen

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

Great solution. Just implemented. Added shortcut following pattern below public static Task GetAsync(this HttpClient httpClient, string uri, string token, CancellationToken cancellationToken) { return httpClient.GetAsync(uri, x => SetToken(x, token), cancellationToken); } void SetToken(HttpRequestMessage request, string token, string type = "Bearer")
What about HttpClientHandler.Proxy, HttpClientHandler.CookieContainer and other properties of HttpClientHandler that cannot be set in the HttpRequestMessage? (or can they?)
@DavidS. did you find the solution for proxy ?
For proxy, cookiecontainer, etc where you have specific needs for individual requests, I believe the recommendation would be to use named or typed clients which have the configuration you desire for those particular requests and then you can use an unnamed client for all others. docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
@hemp - I agree, but that documentation applies to Microsoft.Extensions.DependencyInjection (IServiceCollection), or at least it's less clear how we would use it with a different IoC container or none.