单例 HttpClient 是否在 X 分钟后接收到新的 HttpMessageHandler

Is a Singleton HttpClient receiving a new HttpMessageHandler After X Minutes

我在 Factory 中注册了我的 HttpClient:

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

此 ICatalogService 是通过单例服务中的构造函数注入的。

我会在 2 分钟后在内部收到一个新的 HttpMessageHandler 还是仅当 ICatalogService 在 2 分钟后注入到非单例服务中时?

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

Will I receive after 2 Minutes internally a new HttpMessageHandler or only if ICatalogService would be injected in NOT a Singleton Service after 2 Minutes?

每 2 分钟后,旧的 HttpMessageHandler 将被丢弃,新的 HttpMessageHandler 之后不会立即重新生成,但在您的 HttpClient 发出请求的那一刻。

Basically, does the internal HttpMessageHandler expires also when the wrapping HttpHandler is used as a Singleton?

我不确定你打算如何包装处理程序,因为它必须从 DelegatingHandler 继承? 如果它会像 services.AddSingleton<SomeDelegatingHandler>(); 那么就不要这样做

但是实际上您的 2 行代码在您的用例中非常笨拙...这就是原因

services.AddHttpClient<ICatalogService, CatalogService>() 会将 HttpClient 直接注入到您的 CatalogService,并将您的 CatalogService 注册为 Transient。因此,为什么要在单例实例中注入瞬态服务?这让我很困惑。

Basically, does the internal HttpMessageHandler expires also when the wrapping HttpHandler is used as a Singleton?

警告:通过单例保持 HttpClient 实例存活是安全的。

配置的HttpMessageHandler实例将在两分钟后过期。任何 过期后创建的 HttpClient 将获得一个包含新过期日期的新 HttpMessageHandler。但这对保持活动状态的 HttpClient 实例没有帮助。

重要提示: HttpClient 将一直使用相同的 HttpMessageHandler,因为它会一直使用,因此, not respect DNS changes。只有新 HttpMessageHandler 个实例会看到 DNS 更新。

这意味着只要 HttpClient 保持活动状态,HttpClient 就会错过 DNS 更改,这就是为什么 HttpClient 实例只能存活很短的时间.创建和清理 HttpClient 个实例非常便宜,只要您重用底层 HttpMessageHandler 个实例(这是基础架构为您所做的)。

在您的情况下,不幸的是,您的 HttpClient 被注入到 CatalogService 中,后者被注入到 Singleton 消费者中。 Singleton 使 CatalogService 保持活动状态,因此在应用程序期间间接使 HttpClient 保持活动状态——您的 HttpClient 现在是 Captive Dependency

你遇到的是新 .NET Core IHttpClientFactory 基础结构中的一个不幸的设计缺陷。我认为这是一个缺陷,因为 IMO 基础设施应该阻止你持有 HttpClient 个实例俘虏,例如通过将客户端(您的 CatalogService)注册为 Scoped。我 reported this issue 早在 2019 年 1 月。微软已经承认了这个问题,但在撰写本文时,还没有可用的解决方案。

由于还没有解决方案,你必须非常小心,不要导致 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 核心网站 时,框架将为您验证范围,这会在客户端注入单例时导致异常。

但请注意,使用此扩展方法不会检测所有捕获的 HttpClient 实例。这是因为在 ASP.NET Core 中的某些地方,组件注册为 Transient 时,在应用程序期间仍将保持活动状态。一种这样的情况是 hosted servicesAddHostedService 扩展方法就是这样的例子。托管服务注册为 Transient,同时保持活动状态为 Singleton。在我看来,还有另一个设计缺陷。但这意味着您在直接或间接将 HttpClient 个实例注入托管服务时应该小心。