池化的 HttpClient 实例是不是保留 CookieContainer?

Posted

技术标签:

【中文标题】池化的 HttpClient 实例是不是保留 CookieContainer?【英文标题】:Do pooled HttpClient instances keep the CookieContainer?池化的 HttpClient 实例是否保留 CookieContainer? 【发布时间】:2020-12-10 01:47:15 【问题描述】:

answer 展示了如何将 cookie 添加到 HttpClient 请求。但是其中一个 cmets 警告 CookieContainer 缓存在 HttpClient 上。我的大部分 cookie 不应再次发送给其他用户。

我希望 HttpClientFactory 可以清除池中 HttpClient 实例中添加的 CookieContainers 等内容。

如果我通过调用httpClientFactory.CreateClient 得到我的HttpClient,我得到的那个人是否有可能在上面有以前的CookieContainer

更新:

我的问题的答案是肯定的! CookieContainer 适用于您希望在每次调用时发送相同的 cookie。

如果您需要在每次调用时发送不同的 cookie,有几种方法可以做到这一点。不过最简单的还是放在表头。

因为HttpClientFactory 会为客户端的每个请求创建一个新的HttpClient,所以您可以添加到客户端的标头中,而不必担心它会重复使用 cookie。

这样做的关键是设置UseCookies。它必须设置为 false,否则您添加到标头的 cookie 将被忽略。完成此操作后,您可以将 cookie 添加到“Cookies”键下的标题中,并将它们作为分号分隔的键值对输入(它们之间使用等号)。您的 cookie 在标题“Set-Cookies”中返回。

【问题讨论】:

我不确定此信息是否仍然准确,或者行为是否可能已经改变,但此问题链接来自 ASP.NET Core Docs GitHub 关于此的内容。对我来说似乎很奇怪,考虑到建议使用工厂并重用客户端实例github.com/dotnet/AspNetCore.Docs/pull/15855/files 【参考方案1】:

池化的是 HttpMessageHandler 实例;不是HttpClient 实例。默认情况下,处理程序的生命周期为 2 分钟,这意味着一个CookieContainer 很可能会在多个HttpClients 之间共享。

有一个section in the official docs,它解释了自动cookie处理和IHttpClientFactory不能很好地协同工作:

池化的HttpMessageHandler 实例导致CookieContainer 对象被共享。意外的CookieContainer 对象共享通常会导致错误的代码。对于需要 Cookie 的应用,请考虑:

禁用自动 cookie 处理 避免IHttpClientFactory

调用 ConfigurePrimaryHttpMessageHandler 禁用自动 cookie 处理:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    
        return new SocketsHttpHandler()
        
            UseCookies = false,
        ;
    );

【讨论】:

设置UseCookies = false有什么作用?这是否意味着CookieContainers 没有被缓存,或者它们只是不起作用? (我见过直接将cookie添加为标头的示例,这是UseCookies设置为false的推荐方式吗? 我认为this answer 涵盖了您的两个观点。这意味着您可以使用传统的 HTTP 标头设置 cookie。 This answer 显示了另一种方法,如果您只想从传入请求转发 cookie。【参考方案2】:

找到HttpClientFactory 的实现有点棘手。

多年来,它已从一个仓库转移到另一个仓库:

    aspnet/HttpClientFactory dotnet/Extensions dotnet/Runtime

我们对DefaultHttpClientFactory很感兴趣。

我们先看CreateClient:

public HttpClient CreateClient(string name)

    if (name == null)
    
        throw new ArgumentNullException(nameof(name));
    

    HttpMessageHandler handler = CreateHandler(name);
    var client = new HttpClient(handler, disposeHandler: false);

    HttpClientFactoryOptions options = _optionsMonitor.Get(name);
    for (int i = 0; i < options.HttpClientActions.Count; i++)
    
        options.HttpClientActions[i](client);
    

    return client;

如您所见,它调用CreateHandler 方法来检索HttpMessageHandler

public HttpMessageHandler CreateHandler(string name)

    if (name == null)
    
        throw new ArgumentNullException(nameof(name));
    

    ActiveHandlerTrackingEntry entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;

    StartHandlerEntryTimer(entry);

    return entry.Handler;

_activeHandlers 定义looks like this:

_activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
_entryFactory = (name) =>

    return new Lazy<ActiveHandlerTrackingEntry>(() =>
    
        return CreateHandlerEntry(name);
    , LazyThreadSafetyMode.ExecutionAndPublication);
;

如果您愿意,可以跳转到初始化新处理程序的内部 CreateHandlerEntry 方法,但我认为从主题角度来看它超出了范围。

所以你可以看到,当你得到一个已经存在的实例时,没有任何逻辑可以进行任何类型的清理。通过optionsMonitorHttpClientActionsHttpMessageHandlerBuilderActions 可以注入一些代码,例如they did during testing。

Stephen Gordon has written an article 关于 HttpClient 实例的创建和处置值得一读。

【讨论】:

【参考方案3】:

CookieContainerHttpClient 紧密耦合的事实在我看来是一个主要的设计缺陷,因为它不允许您(正确地)在模拟多个 cookie“会话”时使用后者的单个实例.正如所指出的,HttpClientFactory 并不能解决这个问题 - 你仍然坚持要么放弃 CookieContainer 并自己处理原始标头,要么拥有比你真正需要的更多的 HttpClient 实例。

我最近将solved this problem with Flurl 作为 3.0 的主要新功能。如果您不介意库依赖关系,这都是 HttpClient 的底层,但它用类似的概念 (CookieJar) 替换了 CookieContainer,它支持相同的自动 cookie 处理,但实现了单客户端/多会话可能的场景。

【讨论】:

以上是关于池化的 HttpClient 实例是不是保留 CookieContainer?的主要内容,如果未能解决你的问题,请参考以下文章

总结池化的作用以及avgpool和maxpool适用的场景

基于差分池化的分层图表示方法概述

深度学习—池化padding的理解

HttpClient的使用

卷积和池化的区别

AI算力池化的五大场景揭秘