当使用包装的 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 services。 AddHostedService
扩展方法就是这样的例子。托管服务被注册为Transient
,但在应用程序运行期间保持为Singleton
。在我看来这是另一个设计缺陷。但这意味着您在直接或间接注入HttpClient
实例到托管服务时应该格外小心。
HttpMessageHandler
将在两分钟后被丢弃时在技术上是正确的,但这只会导致新创建的HttpClient
实例获得一个新的HttpMessageHandler
。任何现有的HttpClient
,不幸的是,都将保留对其原始HttpMessageHandler
的引用。这意味着长期存在的HttpClient
实例将不尊重这些过期时间。在问题中,CatalogService
的HttpClient
由单例保持活动状态,这可能会在长期运行中引起问题。请参阅我的答案以获取更多详细信息。 - Steven