基本上,当包装HttpHandler用作Singleton时,内部HttpMessageHandler是否也会过期?
警告:通过单例保持HttpClient 实例处于活动状态是不安全的。
配置的HttpMessageHandler 实例将在两分钟后过期。在到期日期之后创建的任何HttpClient 都将获得一个包含新到期日期的新HttpMessageHandler。但这对保持活动状态的 HttpClient 实例没有帮助。
重要提示: HttpClient 将一直使用相同的 HttpMessageHandler,只要它存在并且将会,因此,
not respect DNS changes。只有新的 HttpMessageHandler 实例才能看到 DNS 更新。
这意味着只要您保持HttpClient 处于活动状态,HttpClient 就会错过 DNS 更改,这就是为什么 HttpClient 实例应该只存活很短的时间。只要您重用底层的HttpMessageHandler 实例(这就是基础架构为您所做的),创建和清理HttpClient 实例就非常便宜。
不幸的是,您的HttpClient 被注入到CatalogService 中,而CatalogService 又被注入到Singleton 消费者中。 Singleton 使 CatalogService 保持活动状态,因此,在应用程序期间间接保持 HttpClient 活动 - 您的 HttpClient 现在是 Captive Dependency。
您遇到的是新的 .NET Core IHttpClientFactory 基础架构中的一个不幸的设计缺陷。我认为这是一个缺陷,因为 IMO 基础设施应该阻止您持有 HttpClient 实例俘虏,例如通过将客户端(您的CatalogService)注册为Scoped。我早在 2019 年 1 月就reported this issue。微软已经承认了这个问题,但在撰写本文时,还没有可用的解决方案。
由于目前还没有解决方案,因此您必须非常小心,不要让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 网站 while debugging 时,框架将为您验证范围,这会在客户端注入单例时导致异常。
但请注意,使用此扩展方法不会检测到所有捕获的 HttpClient 实例。这是因为在 ASP.NET Core 中有一些地方注册为 Transient 的组件在应用程序期间仍然保持活动状态。一种这样的情况是hosted services。 AddHostedService 扩展方法就是这样的例子。托管服务注册为Transient,同时保持活动为Singleton。在我看来,另一个设计缺陷。但这意味着在将HttpClient 实例直接或间接注入托管服务时应该小心。