Do pooled HttpClient instances keep the CookieContainer?

4.5k views Asked by At

This answer shows how to add a cookie to an HttpClient request. But one of the comments warns that CookieContainer is cached on the HttpClient. Most of my cookies should not be sent again for a different user.

I am hoping that the HttpClientFactory cleans things like added CookieContainers off the instance of the HttpClient that is in its pool.

If I get my HttpClient by calling httpClientFactory.CreateClient is there a chance that the one I get will have a previous CookieContainer on it?

Update:

The answer to my question is yes! CookieContainer is intended for when you want to send the same cookie with every call.

If you need to send a different cookie with each call, there are several ways to do that. But the easiest is to put it in the header.

Because the HttpClientFactory creates a new HttpClient with each request for a client, you can add to the headers of the client and not worry about it reusing the cookie.

The key to doing this is the setting UseCookies. It HAS to be set to false or the cookies you add to the headers will be ignored. Once you do that, you can add the cookies to your header under the key "Cookies" and enter them as semicolon separated key value pairs (with an equal sign between them). Your cookies come back in the header "Set-Cookies".

3

There are 3 answers

2
Kirk Larkin On BEST ANSWER

It's the HttpMessageHandler instances that get pooled; not the HttpClient instances. By default, the handlers have a lifetime of 2 minutes, which means it's very possible that a CookieContainer will be shared between multiple HttpClients.

There's a section in the official docs, which explains that automatic cookie handling and IHttpClientFactory don't work well together:

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,
        };
    });
0
Peter Csala On

It's kinda tricky to find the implementation of the HttpClientFactory.

It has been moved from one repo to another over the years:

  1. aspnet/HttpClientFactory
  2. dotnet/Extensions
  3. dotnet/Runtime

We are interested about the DefaultHttpClientFactory.

Let's look at the CreateClient first :

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;
}

As you can see it calls the CreateHandler method to retrieve a 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;
}

The _activeHandlers definition looks like this:

_activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
_entryFactory = (name) =>
{
    return new Lazy<ActiveHandlerTrackingEntry>(() =>
    {
        return CreateHandlerEntry(name);
    }, LazyThreadSafetyMode.ExecutionAndPublication);
};

If you want to you can jump to the internal CreateHandlerEntry method which initalizes a new handler, but I think it is out of scope from the topic perspective.

So as you can see there is no logic which would do any sort of cleanup when you get an already existing instance. Via optionsMonitor's HttpClientActions or HttpMessageHandlerBuilderActions can you inject some code, like they did during testing.

Stephen Gordon has written an article about creation and disposal of HttpClient instances it worths reading it.

0
Todd Menier On

The fact that CookieContainer is tightly coupled to HttpClient is a major design flaw in my opinion, because it doesn't allow you to (correctly) use a single instance of the latter while simulating multiple cookie "sessions". As pointed out, HttpClientFactory doesn't solve this - you're still stuck with either abandoning CookieContainer and processing the raw headers yourself, or having more HttpClient instances than you really need.

I recently solved this problem with Flurl as a major new feature in 3.0. If you don't mind the library dependency, it's all HttpClient under the hood, but it replaces CookieContainer with a similar concept (CookieJar) that supports the same automatic cookie handling but makes single-client/multi-session scenarios possible.