0%

HttpClient doesn't change cookie value per request

Recently I found that the HttpClient doesn’t change cookie value per request, and it will cache the value for 2 minutes after the first request which have set the cookie value sent.

Environment

  • dotnet 5
  • runtime image: mcr.microsoft.com/dotnet/aspnet:5.0

Description

I use typed client in our project and use HttpRequestMessage to set the cookie “user.token” in every request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyHttpClient : IMyHttpClient
{
private readonly HttpClient _httpClient;

public MyHttpClient(HttpClient httpClient)
{
this._httpClient = httpClient;
}

public async Task CallAPI()
{
...

var request = new HttpRequestMessage(HttpMethod.Post, "/the/api");
// set request content
request.Content = new StringContent("mydata", Encoding.UTF8, "application/json");
// set cookie here
request.Headers.Add("Cookie", $"user.token={token}");

var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var res = await response.Content.ReadAsStringAsync();

...
}
}

Register MyHttpClient to the Denpency Injection provider.

1
2
3
4
5
services.AddHttpClient<IMyHttpClient, MyHttpClient>(config =>
{
config.BaseAddress = new Uri("https://my.server.com");
});

When I use CallAPI() of MyHttpClient, I found the cookie doesn’t change value per request, and it will cache value for 2 minutes.

Why

HttpClient is just a container of HttpClientHandler. If you trace the source code on Github you will see the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public HttpClient CreateClient(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}

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

var options = _optionsMonitor.Get(name);
for (var i = 0; i < options.HttpClientActions.Count; i++)
{
options.HttpClientActions[i](client);
}

return client;
}

public HttpMessageHandler CreateHandler(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}

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

StartHandlerEntryTimer(entry);

return entry.Handler;
}

When you are asking for HttpClient, HttpClientFactory will generate a HttpClient instance everytime and try to get HttpMessageHandler from the pool. Why HttpClientFactory maintains HttpMessageHandler pool for us? Because creating TCP connections are extremely expensive, so we should reuse it as we can as possible.

So multiple HttpClients will possibly use same HttpMessageHandler. HttpMessageHandler has Property named CookieContainer. It will store cookie value when you first time to set the value. So it will cause different requests using same cookie value until HttpMessageHandler expired.

Solution

There is a way to stop HttpMessageHandler using CookieContainer.

1
2
3
4
5
6
7
8
9
services.AddHttpClient<IPortalShellClient, PortalShellHttpClient>(config =>
{
config.BaseAddress = new Uri(clientConfig.PortalSiteEndpoint);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
//Tell HttpMessageHandler to stop using CookieContainer
return new HttpClientHandler { UseCookies = false };
});

Then you can set cookie value per request now.

References