dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.97k stars 4.66k forks source link

A better way to manage cookies and session for HttpClient #77668

Open ziaulhasanhamim opened 1 year ago

ziaulhasanhamim commented 1 year ago

Background and motivation

In HttpClient there is a behavior of one cookie container per HttpClientHandler instead of one per HttpClient. This behavior looks very weird and unnecessary to me. Because the HttpClientHandler is mostly cached. One CookieContainer per HttpClient is the intended behavior for almost all use cases. I don't think the current behavior is useful in any way and have no idea why .Net took this path with HttpClient. Although I'm not a big fan of HttpClientFactory but still when I'm using it handling cookies is complicated with it because of one cookie container per HttpClientHandler.

API Proposal

The best solution I can think of is to introduce a Session class(or even struct) that will manage the session for the requests made with it.

public class Session : IDisposable
{
    public CookieContainer CookieContainer { get; init; } = DefaultOne;

    public bool UseCookies { get; init; } = true;

    // more fields related to state management like Credentials, AllowAutoRedirect and all

    public HttpRequestHeaders DefaultRequestHeaders { get; } = new HttpRequestHeaders();

    public Uri BaseAddress { get; init; }

    // same apis for send, get, post, put, delete, patch etc like HttpClient
}

Sessions are very very useful. It helps to group certain requests. It would simplify the usage of HttpClient specially when using HttpClient as a singleton with a pooled connection.

API Usage

The session will manage the cookies, headers, and all.

var client = GetCachedClient();
using var session = new Session(client)
{
  BaseAddress = ....
};
session.DefaultHeaders.Add(....);
var res1 = await session.PostAsync(path1, req);
var res2 = await session.GetAsync(path2);

How nice is that.

Alternative Designs

An alternate way would be to add CookieContainers and all to the HttpClient itself. But Session objects are way easier to use and very simple

Risks

No response

ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation In HttpClient there is a behavior of one cookie container per HttpClientHandler instead of one per HttpClient. This behavior looks very weird and unnecessary to me. **Because the HttpClientHandler is mostly cached**. One CookieContainer per HttpClient is the intended behavior for almost all use cases. I don't think the current behavior is useful in any way and have no idea why .Net took this path with HttpClient. Although I'm not a big fan of HttpClientFactory but still when I'm using it handling cookies is complicated with it because of one cookie container per HttpClientHandler. ### API Proposal The best solution I can think of is to introduce a Session class(or even struct) that will manage the session for the requests made with it. ```CS public class Session : IDisposable { public CookieContainer CookieContainer { get; init; } = DefaultOne; public bool UseCookies { get; init; } = true; // more fields related to state management like Credentials, AllowAutoRedirect and all public HttpRequestHeaders DefaultRequestHeaders { get; } = new HttpRequestHeaders(); public Uri BaseAddress { get; init; } // same apis for send, get, post, put, delete, patch etc like HttpClient } ``` Sessions are very very useful. It helps to group certain requests. It would simplify the usage of HttpClient specially when using HttpClient as a singleton with a pooled connection. ### API Usage The session will manage the cookies, headers, and all. ```CS var client = GetCachedClient(); using var session = new Session(client) { BaseAddress = .... }; session.DefaultHeaders.Add(....); var res1 = await session.PostAsync(path1, req); var res2 = await session.GetAsync(path2); ``` How nice is that. ### Alternative Designs An alternate way would be to add CookieContainers and all to the HttpClient itself. But Session objects are way easier to use and very simple ### Risks _No response_
Author: ziaulhasanhamim
Assignees: -
Labels: `api-suggestion`, `area-System.Net.Http`
Milestone: -
ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation In HttpClient there is a behavior of one cookie container per HttpClientHandler instead of one per HttpClient. This behavior looks very weird and unnecessary to me. **Because the HttpClientHandler is mostly cached**. One CookieContainer per HttpClient is the intended behavior for almost all use cases. I don't think the current behavior is useful in any way and have no idea why .Net took this path with HttpClient. Although I'm not a big fan of HttpClientFactory but still when I'm using it handling cookies is complicated with it because of one cookie container per HttpClientHandler. ### API Proposal The best solution I can think of is to introduce a Session class(or even struct) that will manage the session for the requests made with it. ```CS public class Session : IDisposable { public CookieContainer CookieContainer { get; init; } = DefaultOne; public bool UseCookies { get; init; } = true; // more fields related to state management like Credentials, AllowAutoRedirect and all public HttpRequestHeaders DefaultRequestHeaders { get; } = new HttpRequestHeaders(); public Uri BaseAddress { get; init; } // same apis for send, get, post, put, delete, patch etc like HttpClient } ``` Sessions are very very useful. It helps to group certain requests. It would simplify the usage of HttpClient specially when using HttpClient as a singleton with a pooled connection. ### API Usage The session will manage the cookies, headers, and all. ```CS var client = GetCachedClient(); using var session = new Session(client) { BaseAddress = .... }; session.DefaultHeaders.Add(....); var res1 = await session.PostAsync(path1, req); var res2 = await session.GetAsync(path2); ``` How nice is that. ### Alternative Designs An alternate way would be to add CookieContainers and all to the HttpClient itself. But Session objects are way easier to use and very simple ### Risks _No response_
Author: ziaulhasanhamim
Assignees: -
Labels: `api-suggestion`, `untriaged`, `area-Extensions-HttpClientFactory`
Milestone: -
ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation In HttpClient there is a behavior of one cookie container per HttpClientHandler instead of one per HttpClient. This behavior looks very weird and unnecessary to me. **Because the HttpClientHandler is mostly cached**. One CookieContainer per HttpClient is the intended behavior for almost all use cases. I don't think the current behavior is useful in any way and have no idea why .Net took this path with HttpClient. Although I'm not a big fan of HttpClientFactory but still when I'm using it handling cookies is complicated with it because of one cookie container per HttpClientHandler. ### API Proposal The best solution I can think of is to introduce a Session class(or even struct) that will manage the session for the requests made with it. ```CS public class Session : IDisposable { public CookieContainer CookieContainer { get; init; } = DefaultOne; public bool UseCookies { get; init; } = true; // more fields related to state management like Credentials, AllowAutoRedirect and all public HttpRequestHeaders DefaultRequestHeaders { get; } = new HttpRequestHeaders(); public Uri BaseAddress { get; init; } // same apis for send, get, post, put, delete, patch etc like HttpClient } ``` Sessions are very very useful. It helps to group certain requests. It would simplify the usage of HttpClient specially when using HttpClient as a singleton with a pooled connection. ### API Usage The session will manage the cookies, headers, and all. ```CS var client = GetCachedClient(); using var session = new Session(client) { BaseAddress = .... }; session.DefaultHeaders.Add(....); var res1 = await session.PostAsync(path1, req); var res2 = await session.GetAsync(path2); ``` How nice is that. ### Alternative Designs An alternate way would be to add CookieContainers and all to the HttpClient itself. But Session objects are way easier to use and very simple ### Risks _No response_
Author: ziaulhasanhamim
Assignees: -
Labels: `api-suggestion`, `area-System.Net.Http`, `untriaged`
Milestone: -
ManickaP commented 1 year ago

Sharing some notes from @CarnaViire:

We've discussed several asks for per-request/per-session things for HttpClient, such as:

Main ask is Cookies, as currently CookieContainer is tied to HttpMessageHandler, which is very inconvenient. Today you have to either always turn the cookies off and potentially track them manually, or to create several HttpClients and carefully manage their lifetime and usages -- there're no good workarounds.

Potential solution: add CookieContainer parameter either to HttpRequestMessage or to SendAsync overload.

Downsides: APIs like GetAsync/GetAsStringAsync etc will not get this parameter (too many overloads, and they don't expose HttpRequestMessage)

Open questions:

Triage: we are aware this is a pain point for the users and want to solve this, but do it the right way, which is not as straight-forward as it might seem and needs careful design. For this, I'm moving it to future for now. We can always revisit and change the milestone.

ziaulhasanhamim commented 1 year ago

Potential solution: add CookieContainer parameter either to HttpRequestMessage or to SendAsync overload.

This will somehow solve the problem but this is also not that convenient

Golang uses a cookie jar. This model is pretty similar to C# and not so helpful.

As mentioned I have seen python solving this in the best way possible. They use session objects. Sessions are a way to group some requests based on common behavior regarding cookies, headers, and even middleware handlers.