从手动管理的 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 就可以发挥其背景魔法。