合并的 HttpClient 实例是否保留 CookieContainer?

Do pooled HttpClient instances keep the CookieContainer?

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

我希望 HttpClientFactory 可以清除其池中 HttpClient 实例中添加的 CookieContainers 之类的东西。

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

更新:

我的问题的答案是肯定的! CookieContainer 适用于每次调用都发送相同 cookie 的情况。

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

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

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

找到 HttpClientFactory 的实现有点棘手。

这些年来,它已从一个存储库移至另一个存储库:

  1. aspnet/HttpClientFactory
  2. dotnet/Extensions
  3. 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 实例的创建和处理值得一读。

HttpMessageHandler 个实例被合并;不是 HttpClient 个实例。默认情况下,handlers 的生命周期为 2 分钟,这意味着 非常 有可能 CookieContainer 将在多个 HttpClients.

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

The pooled HttpMessageHandler instances results in CookieContainer objects being shared. Unanticipated CookieContainer object sharing often results in incorrect code. For apps that require cookies, consider either:

  • Disabling automatic cookie handling
  • Avoiding IHttpClientFactory

Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

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

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

我最近 solved this problem with Flurl 作为 3.0 的主要新功能。如果您不介意库依赖性,它都是 HttpClient 在幕后,但它用类似的概念 (CookieJar) 替换了 CookieContainer,它支持相同的自动 cookie 处理,但使 single-client/multi-session 可能的情况。