从手动管理的 HttpClient(使用单独的 TokenUri)迁移到 IHttpClientFactory
Migrating from manually managed HttpClient (with separate TokenUri) to IHttpClientFactory
我的 Web 应用程序无法释放内存。
我怀疑 HttpClient 是问题之一,因为 HttpClient 的对象数随着时间的推移而增加。
因此我想迁移到托管的 IHttpClientFactory,但现在我对如何最好地实现对令牌服务的调用感到困惑(我考虑过使用 typed client variant)。
现在是这样实现的:
var myClient = new MyClient(credentials, baseUri, tokenUri, timeout);
在 MyClient HttpClient(1) 内部负责调用令牌服务(凭据、tokenUri)、存储到期日期并将不记名令牌返回给调用端点(baseUri、超时)的 HttpClient(2)。
如果 myClient 现在尝试获取一些数据,它会检查令牌是否需要刷新,如果不需要,它会获取数据。
我将如何使用 IHttpClientFactory 执行此操作?
我是否仍然需要自己处理 HttpClient(1)(过期日期),或者工厂会以某种方式检测是否需要刷新令牌?
我至少明白,工厂决定连接是否保持打开状态。
听起来您在向 HttpClientFactory 的过渡方面走在了正确的轨道上,尤其是类型化的 HttpClient。
在幕后,HttpClientFactory 的默认实现管理着底层主消息处理程序的池化和处置,这意味着位于其之上的实际 HttpClient 可以开始以范围内的方式生成和处置,而不是试图管理它的一些全局的、长的 运行 实例或创建和拆除一次性实例,这在 Microsoft 自己的文档中有很好的描述:Use IHttpClientFactory to implement resilient HTTP requests
在像您这样的 HttpClient 可能长期存在的情况下,客户端本身在其实例(例如令牌)内管理状态可能是有意义的,但您最终需要采取不同的路径现在可以(并且应该)更频繁地处理客户端。
我是否还需要自己处理 HttpClient(1)(过期日期),或者工厂会以某种方式检测是否需要刷新令牌?
是的,您仍然需要处理它,但是 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);
}
}
当您在开始时进行服务注册并将令牌提供者注册为单例时,您的所有状态(例如令牌)不再是客户端本身的一部分,因此您的客户端现在可以被处置和注入任何地方。该提供程序也可以注销到缓存或数据库中。
这仍然有点笨拙,因为它仍然将所有用于调用、失败、更新身份验证、重试等的逻辑放在您键入的客户端逻辑中——如果它涵盖了您的需要,它可能就足够了,或者您可能想要更强大的东西。 HttpClientFactory 可以轻松添加委托处理程序管道以及 policies for resiliency with Polly,例如重试:
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 不需要更聪明(甚至根本不需要处理身份验证)。
关键要点,请确保在过渡到此模式时,当您处理完所创建的客户端时,无论它们处于什么范围,这样 HttpClientFactory 就可以发挥其背景魔法。
我的 Web 应用程序无法释放内存。
我怀疑 HttpClient 是问题之一,因为 HttpClient 的对象数随着时间的推移而增加。
因此我想迁移到托管的 IHttpClientFactory,但现在我对如何最好地实现对令牌服务的调用感到困惑(我考虑过使用 typed client variant)。
现在是这样实现的:
var myClient = new MyClient(credentials, baseUri, tokenUri, timeout);
在 MyClient HttpClient(1) 内部负责调用令牌服务(凭据、tokenUri)、存储到期日期并将不记名令牌返回给调用端点(baseUri、超时)的 HttpClient(2)。
如果 myClient 现在尝试获取一些数据,它会检查令牌是否需要刷新,如果不需要,它会获取数据。
我将如何使用 IHttpClientFactory 执行此操作?
我是否仍然需要自己处理 HttpClient(1)(过期日期),或者工厂会以某种方式检测是否需要刷新令牌?
我至少明白,工厂决定连接是否保持打开状态。
听起来您在向 HttpClientFactory 的过渡方面走在了正确的轨道上,尤其是类型化的 HttpClient。
在幕后,HttpClientFactory 的默认实现管理着底层主消息处理程序的池化和处置,这意味着位于其之上的实际 HttpClient 可以开始以范围内的方式生成和处置,而不是试图管理它的一些全局的、长的 运行 实例或创建和拆除一次性实例,这在 Microsoft 自己的文档中有很好的描述:Use IHttpClientFactory to implement resilient HTTP requests
在像您这样的 HttpClient 可能长期存在的情况下,客户端本身在其实例(例如令牌)内管理状态可能是有意义的,但您最终需要采取不同的路径现在可以(并且应该)更频繁地处理客户端。
我是否还需要自己处理 HttpClient(1)(过期日期),或者工厂会以某种方式检测是否需要刷新令牌?
是的,您仍然需要处理它,但是 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);
}
}
当您在开始时进行服务注册并将令牌提供者注册为单例时,您的所有状态(例如令牌)不再是客户端本身的一部分,因此您的客户端现在可以被处置和注入任何地方。该提供程序也可以注销到缓存或数据库中。
这仍然有点笨拙,因为它仍然将所有用于调用、失败、更新身份验证、重试等的逻辑放在您键入的客户端逻辑中——如果它涵盖了您的需要,它可能就足够了,或者您可能想要更强大的东西。 HttpClientFactory 可以轻松添加委托处理程序管道以及 policies for resiliency with Polly,例如重试:
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 不需要更聪明(甚至根本不需要处理身份验证)。
关键要点,请确保在过渡到此模式时,当您处理完所创建的客户端时,无论它们处于什么范围,这样 HttpClientFactory 就可以发挥其背景魔法。