单例模式的HttpClient在X分钟后是否会接收一个新的HttpMessageHandler

3

我在工厂中注册了我的HttpClient:

services.AddHttpClient<ICatalogService, CatalogService>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(2));

这个ICatalogService通过构造函数注入到单例服务中。

如果ICatalogService被注入到非单例服务中,那么2分钟后会收到一个新的HttpMessageHandler吗?

基本上,当封装的HttpHandler被用作单例时,内部的HttpMessageHandler是否也会过期?

2个回答

5

当使用包装的 HttpHandler 作为单例时,内部的 HttpMessageHandler 是否也会过期?

警告:HttpClient 实例通过单例模式保持活动状态是不安全的

已配置的 HttpMessageHandler 实例将在两分钟后过期。在到期日之后创建的任何 HttpClient 实例都将获得一个新的 HttpMessageHandler,其中包含新的到期日期。但这对于保持活动状态的 HttpClient 实例无济于事。

重要提示:只有在每次创建新的 HttpMessageHandler 实例时才能看到DNS更新,HttpClient 实例会一直使用相同的 HttpMessageHandler,因此不会尊重DNS更改

这意味着只要保持 HttpClient 存活,该 HttpClient 将会错过 DNS 更改,这就是为什么 HttpClient 实例应该只存在于短时间内的原因。只要重用基础的 HttpMessageHandler 实例(这就是基础架构为您完成的操作),创建和清理 HttpClient 实例非常便宜。
不幸的是,在您的情况下,您的 HttpClient 被注入到 CatalogService 中,而该 CatalogService 又被注入到一个 Singleton 消费者中。该 Singleton 使得 CatalogService 存活,并因此间接地使得 HttpClient 在应用程序的整个生命周期内存活 - 您的 HttpClient 现在成为了 Captive Dependency
您正在经历的是新的.NET Core IHttpClientFactory基础设施中不幸的设计缺陷。我认为这是一个缺陷,因为基础设施应该防止您持有HttpClient实例,例如通过将客户端(您的CatalogService)注册为Scoped。我在2019年1月报告了此问题。Microsoft已经承认了这个问题,但截至本文撰写时,没有可用的解决方案。
由于目前还没有解决方案,您必须非常小心,不要导致HttpClient作为Captive Dependency保持存活。这意味着您不能将其注入到Singleton消费者中(甚至间接的消费者也不行)。
防止这种情况发生的好方法是将客户端(您的CatalogService)注册为Scoped。这允许框架的配置系统验证它是否被注入到Singleton中。但由于没有直接支持这一点,您将不得不手动进行此注册,例如使用以下扩展方法:
public static IHttpClientBuilder AddTypedClientScoped<TClient>(
  this IHttpClientBuilder builder)
  where TClient : class
{
    ...
    builder.Services.AddScoped<TClient>(s =>
    {
        var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
        var httpClient = httpClientFactory.CreateClient(builder.Name);
        var factory = s.GetRequiredService<ITypedHttpClientFactory<TClient>>();
        return factory.CreateClient(httpClient);
    });

    return builder;
}

在调试 ASP.NET Core 网站, 框架会为您验证作用域,如果客户端被注入到单例中,则会引发异常。

但请注意,使用此扩展方法无法检测所有固定的HttpClient实例。这是因为在 ASP.NET Core 中有一些组件被注册为Transient,但仍然在应用程序运行期间保持活动状态。其中一个例子是hosted servicesAddHostedService 扩展方法就是这样的例子。托管服务被注册为Transient,但在应用程序运行期间保持为Singleton。在我看来这是另一个设计缺陷。但这意味着您在直接或间接注入HttpClient实例到托管服务时应该格外小心。


-1
每2分钟后,旧的HttpMessageHandler将被丢弃,新的HttpMessageHandler不会立即重新生成,而是在您的HttpClient发出请求时才会生成。
如果使用包装的HttpHandler作为单例,基本上内部的HttpMessageHandler是否过期?
我不确定您将如何包装处理程序,因为它必须从DelegatingHandler继承?
如果像这样services.AddSingleton<SomeDelegatingHandler>();,那么就不要这样做。
但实际上,在您的用例中,您的两行代码非常尴尬...原因如下: services.AddHttpClient<ICatalogService, CatalogService>()将直接向您的CatalogService注入HttpClient,并将您的CatalogService注册为Transient。因此,在单例实例中注入短暂服务是非常混乱的。

1
嗨,Gordon,虽然你在说旧的HttpMessageHandler将在两分钟后被丢弃时在技术上是正确的,但这只会导致新创建的HttpClient实例获得一个新的HttpMessageHandler。任何现有的HttpClient,不幸的是,都将保留对其原始HttpMessageHandler的引用。这意味着长期存在的HttpClient实例将不尊重这些过期时间。在问题中,CatalogServiceHttpClient由单例保持活动状态,这可能会在长期运行中引起问题。请参阅我的答案以获取更多详细信息。 - Steven
1
我已经阅读了您的解释,实际上我错过了指向参考部分。感谢@Steven启发我。 - Gordon Khanh Ng.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接