我有一个网络应用程序,缺乏释放内存的能力。我怀疑HttpClient是其中一个问题,因为HttpClient的对象数量随着时间的推移而增加。
因此,我想迁移到托管的IHttpClientFactory,但现在我被如何最好地实现对token服务的调用所困住了(我想过使用IHttpClientFactory中的 异体字).
现在是这样实现的。
var myClient = new MyClient(credentials, baseUri, tokenUri, timeout);
在MyClient内部,HttpClient(1)负责调用token服务(credentials, tokenUri),存储到期日期,并将承载token返回给调用端点的HttpClient(2)(baseUri, timeout)。 如果myClient现在试图获取一些数据,它就会检查token是否需要刷新,如果不需要就会获取数据。
我如何用IHttpClientFactory来做这件事? 我是否还需要自己处理HttpClient(1)(到期日),还是工厂会以某种方式检测是否需要刷新token? 我至少明白,工厂会决定一个连接是否保持开放。
听起来你向HttpClientFactory的过渡是正确的,尤其是类型化的HttpClient。
在引擎盖下,HttpClientFactory的默认实现管理底层主消息处理程序的池化和处置,这意味着坐在它上面的实际HttpClient可以开始以一种范围化的方式生成和处置,而不是试图管理它的一些全局的、长期运行的实例,或者创建和拆解一次性的实例,这在微软自己的文档中得到了很好的描述。使用IHttpClientFactory来实现弹性的HTTP请求。
在像你这样的情况下,HttpClient可能是长寿命的,客户端本身在其实例中管理状态(比如token)可能是有意义的,但你最终需要走一条不同的道路,现在客户端可以(也应该)更频繁地被处理掉。
我是否仍然需要自己处理HttpClient(1)(到期日),还是工厂会以某种方式检测是否需要刷新token?
是的,你仍然需要处理它,但是HttpClientFactory模式给了你一些工具来帮助管理它。由于你使用HttpClientFactory本质上是倾向于依赖注入,你可能会有几种不同的路径。
在最基本的情况下,只是添加某种单人令牌提供者,它为你管理令牌,并可以由DI容器注入到类型化的客户端。
public interface ITokenProvider
{
string GetToken(string key);
void StoreToken(string key, string token);
}
// Incredibly basic example, not thread safe, etc...
public class InMemoryTokenProvider : ITokenProvider
{
private readonly Dictionary<string, string> _tokenList = new Dictionary<string, string>();
public string GetToken(string key)
{
return _tokenList.GetValueOrDefault(key);
}
public void StoreToken(string key, string token)
{
_tokenList.Remove(key); // upsert, you get the point...
_tokenList.Add(key, token);
}
}
public class TypedClient
{
private readonly HttpClient _client;
private readonly ITokenProvider _tokenProvider;
public TypedClient(HttpClient client, ITokenProvider tokenProvider)
{
_client = client;
_tokenProvider = tokenProvider;
}
public async Task DoYourThing()
{
var token = _tokenProvider.GetToken("token_A");
// ... if it failed, then UpdateTheAuth()
}
private async Task UpdateTheAuth()
{
var result = await _client.GetAsync("the auth process");
string token = "whatever";
// ...
_tokenProvider.StoreToken("token_A", token);
}
}
当你在一开始就进行服务注册,并将令牌提供者注册为单人时,你所有的状态(比如令牌)都不再是客户端本身的一部分,所以你的客户端现在可以在任何地方被处置和注入。该提供者也可以被写入缓存或数据库中。
这可能仍然有点笨拙,因为它仍然把所有的调用、失败、更新auth、重试等逻辑放在你的类型化客户端逻辑中--如果这能满足你的需求,它可能就足够好了,或者你可能想要更强大的东西。HttpClientFactory可以很容易地添加一个委托处理程序管道以及 弹性政策与波利例如重试。
services.AddTransient<ExampleDelegatingHandler>();
services.AddHttpClient<IMyHttpClient, MyHttpClient>()
.AddHttpMessageHandler<TokenApplicationHandler>()
.AddPolicyHandler(GetRetryPolicy()); // see Microsoft link
委托处理程序管道连接到你的类型化客户端,并像中间件一样为每一个请求和响应运行(并且可以在飞行中修改它们),所以你甚至可以将其中的一些令牌管理转移到委托处理程序中来代替。
public class TokenApplicationHandler : DelegatingHandler
{
private readonly ITokenProvider _tokenProvider;
private readonly IAuthRenewerClient _authRenewer;
public TokenApplicationHandler(ITokenProvider tokenProvider, IAuthRenewerClient authRenewer)
{
_tokenProvider = tokenProvider;
_authRenewer = authRenewer;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// All just demo level, take the implementation with a grain of salt...
string token = _tokenProvider.GetToken("token_A");
request.Headers.Add("x-token-header", token);
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.Unauthorized)
{
string newToken = _authRenewer.RefreshAuth();
_tokenProvider.StoreToken("token_A", newToken);
}
return response;
}
}
配合重试策略,现在只要一个请求发出去,然后返回来的是一个... Unauthorized
响应,你的委托处理程序可以处理更新,然后请求被重新发送新的令牌,而你的类型化的HttpClient不需要任何明智的做法(或者甚至根本不需要处理auth)。
关键要点,当你过渡到这种模式时,请确保当你完成了你正在创建的客户机的任何范围时,你正在处置它们,这样HttpClientFactory就可以发挥它的后台魔力。