是单例 HttpClient X 分钟后接收新的 HttpMessageHandler
Posted
技术标签:
【中文标题】是单例 HttpClient X 分钟后接收新的 HttpMessageHandler【英文标题】:Is a Singleton HttpClient receiving a new HttpMessageHandler After X Minutes 【发布时间】:2021-08-17 15:22:22 【问题描述】:我在工厂注册了我的 HttpClient:
services.AddHttpClient<ICatalogService, CatalogService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(2));
这个 IDatalogService 是通过 Singleton Service 中的构造函数注入的。
我会在 2 分钟后在内部收到一个新的 HttpMessageHandler,还是仅在 2 分钟后将 IcatalogService 注入到非单例服务中?
基本上,当包装 HttpHandler 时,内部 HttpMessageHandler 是否也会过期 用作单例?
【问题讨论】:
【参考方案1】:基本上,当包装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
实例直接或间接注入托管服务时应该小心。
【讨论】:
【参考方案2】:我会在 2 分钟后在内部收到一个新的 HttpMessageHandler,还是仅在 2 分钟后将 IcatalogService 注入到非单例服务中?
每 2 分钟后,旧的 HttpMessageHandler
将被丢弃,而新的 HttpMessageHandler
在此之后不会立即重新生成,而是在您的 HttpClient
发出请求的那一刻。
基本上,当包装HttpHandler用作Singleton时,内部HttpMessageHandler是否也会过期?
我不确定你将如何包装处理程序,因为它必须从 DelegatingHandler
继承?
如果它会像services.AddSingleton<SomeDelegatingHandler>();
那么干脆不要这样做
但实际上,您的 2 行代码在您的用例中非常尴尬……这就是原因
services.AddHttpClient<ICatalogService, CatalogService>()
将 HttpClient 直接注入您的CatalogService
,并将您的CatalogService
注册为Transient
。因此,为什么要在单例实例中注入临时服务?这让我很困惑。
【讨论】:
嗨,Gordon,虽然您在技术上正确地说旧的HttpMessageHandler
将在两分钟后被丢弃,但这只会导致新创建的HttpClient
实例获得新的HttpMessageHandler
。不幸的是,任何现有的HttpClient
都将保留其对原始HttpMessageHandler
的引用。这意味着长期存在的HttpClient
实例将不会遵守这些到期时间。在问题中,CatalogService
的 HttpClient
由 Singleton 保持活动状态,从长远来看可能会导致问题。有关详细信息,请参阅我的答案。
我已经阅读了您的解释,实际上我错过了参考指向部分。感谢您启发我@Steven以上是关于是单例 HttpClient X 分钟后接收新的 HttpMessageHandler的主要内容,如果未能解决你的问题,请参考以下文章