具有不同身份验证标头的HttpClient单个实例


78

鉴于.net HttpClient在设计时考虑了重用,并且旨在长期存在,并且在短期实例中已报告内存泄漏。在为多个用户调用端点时,要使用不同的承载令牌(或任何授权标头)对给定端点进行静态调用的地方有哪些指导方针?

private void CallEndpoint(string resourceId, string bearerToken) {
  httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("bearer", bearerToken);
  var response = await httpClient.GetAsync($"resource/{resourceid}");
}

鉴于上述代码可以由Web应用程序上的任意数量的线程调用,因此很可能第一行中设置的标头与调用资源时使用的标头不同。

在不使用锁引起争用和维护无状态Web应用程序的情况下,为单个端点创建和部署HttpClient的推荐方法是什么(我目前的做法是为每个端点创建单个客户端)?


生命周期

尽管HttpClient确实间接实现了IDisposable接口,但是HttpClient的推荐用法是不要在每次请求后都将其丢弃。只要您的应用程序需要发出HTTP请求,HttpClient对象就可以存在。在多个请求中都存在一个对象,这为设置DefaultRequestHeaders提供了空间,并且使您不必像HttpWebRequest那样在每个请求中都重新指定诸如CredentialCache和CookieContainer之类的内容。


您是在谈论相对较少的不同身份验证标头还是很多,例如每个用户唯一的身份验证标头?
Todd Menier

@ToddMenier-每个用户的头将是唯一的。可能是用户oauth令牌。我认为斯科特·汉嫩(Scott Hannen)使我走上了正确的道路。看起来有些扩展方法会井然有序。
Bronumski '16

您好@Bronumski,您可以分享解决这个问题的方法吗?我在多个线程添加相同标头但内容不同的问题上遇到了同样的问题。
路易斯·梅加

@LuisMejia-我更新了斯科特答案,并提供了有关如何进行GET和POST的示例。您要实现的任何其他方法都使用相同的原理。扩展方法包括一个操作,该操作使您可以在HttpRequest发送之前对其进行操作。
Bronumski '16

@Bronumski感谢您的回答...似乎我正在以类似的方式利用sendasync并通过自定义标头将请求作为参数传递。
路易斯·梅加

Answers:


80

如果标题通常相同,则可以设置DefaultRequestHeaders。但是您不需要使用该属性来指定标题。正如您已经确定的那样,如果您要使用同一个客户端使用多个线程,那将是行不通的。在一个线程上更改默认标头会影响在其他线程上发送的请求。

尽管您可以在客户端上设置默认标头并将其应用于每个请求,但是标头实际上是请求的属性。因此,当标头特定于请求时,只需将它们添加到请求中即可。

request.Headers.Authorization = new AuthenticationHeaderValue("bearer", bearerToken);

这意味着您不能使用不涉及创建的简化方法HttpRequest。您需要使用

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)

记录在这里


有些人发现使用扩展方法将更新标头的代码与方法的其余部分隔离会很有帮助。

通过扩展方法完成的GET和POST方法示例,该方法使您可以在发送请求标头以及更多请求标头HttpRequestMessage之前对其进行操作:

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

然后可以像下面这样使用它们:

var response = await httpClient.GetAsync("token",
    x => x.Headers.Authorization = new AuthenticationHeaderValue("basic", clientSecret));

很好的解决方案。刚刚实施。在公共静态Task <HttpResponseMessage> GetAsync下面添加了快捷方式以下模式(此HttpClient httpClient,字符串uri,字符串令牌,CancellationToken cancelleToken)} void SetToken(HttpRequestMessage请求,字符串令牌,字符串类型=“ Bearer”)
Osa E

2
怎么样HttpClientHandler.ProxyHttpClientHandler.CookieContainer和其他性能HttpClientHandler不能在设置HttpRequestMessage?(或者可以吗?)
David S.

@DavidS。您找到代理解决方案了吗?
杰伊·沙

1
对于您对单个请求有特定需求的代理,cookiecontainer等,我认为建议是使用具有您希望的特定请求配置的命名或类型客户机,然后对所有其他请求使用未命名客户机。docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
大麻

@hemp-我同意,但是该文档适用于Microsoft.Extensions.DependencyInjectionIServiceCollection),或者至少不清楚我们如何将其用于其他IoC容器或不使用它。
Scott Hannen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.