Open h-arshad opened 10 months ago
Based on what I see at https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.InvokeNativeHandler.cs
This probably has something to do with the differences between the native handlers on iOS vs Android. i.e. Xamarin.Android.Net.AndroidMessageHandler, Mono.Android vs System.Net.Http.NSUrlSessionHandler, Microsoft.iOS
If a redirect is involved, it might be related to #2059, although I wonder why the behavior would differ between mobile platforms. :(
Thank you for your response and looking into the post.
In fact I do not suspect the issue comes from those handlers because eventually due to the cookie issue I had with Restsharp
, I replaced it with the native HttpClient
and it works fine on both platforms.
I put an example of the working code below.
private static readonly System.Net.Http.HttpClientHandler HttpClientMessageHandler = new()
{
AllowAutoRedirect = false,
UseCookies = true,
CookieContainer = new(),
};
private static readonly System.Net.Http.HttpClient RestClient = new(HttpClientMessageHandler)
{
BaseAddress = new("https://google.com"),
Timeout = TimeSpan.FromSeconds(60),
DefaultRequestHeaders =
{
{ "User-Agent", "mobile"},
{ "Accept", "application/json" }
}
};
The one can simply use var response = await RestClient.GetAsync();
@h-arshad RestSharp handles cookies without using an external CookieContainer
. It might have worked if you have just removed the cookie container from the client setup.
It could also be that .NET on iOS has some differences concerning getting cookies from the container based on the URL.
Basically, what RestSharp does: it creates a new cookie container for each request, unless the request itself has a cookie container. But, the container is not used for making a call. Instead, RestSharp adds cookie headers to the request.
When RestSharp receives a response with SetCookie
headers, it adds those values to the cookie container.
The code looks like this:
public static void AddCookies(this CookieContainer cookieContainer, Uri uri, IEnumerable<string> cookiesHeader) {
foreach (var header in cookiesHeader) {
try {
cookieContainer.SetCookies(uri, header);
}
catch (CookieException) {
// Do not fail request if we cannot parse a cookie
}
}
}
You see there that sometimes adding a cookie to the container fails. I am not sure if that's what happens on iOS.
@h-arshad I have the same issue with RestClient not returning the cookies on MAUI for iOS.
I was able to resolve it by passing an instance of HttpClient which had the CookieContainer
set on its HttpClientHandler (similar to the working code you posted above).
I think setting it on the handler via RestClientOptions.ConfigureMessageHandler should probably work too, though i haven't tested it.
The below code would probably also work with IHttpClientFactory, by using that to create the HttpClient instance(and configuring the CookieContainer), but again not tested.
var handler = new HttpClientHandler { CookieContainer = _cookieManager };
var client = new HttpClient(handler);
var options = new RestClientOptions()
{
BaseUrl = new Uri(Connection.Url),
Timeout = TimeSpan.FromSeconds(60),
CookieContainer = _cookieManager,
};
return new RestClient(client, options);
@alexeyzimarev its feels like the ConfigureHttpMessageHandler
method should be applying Options.CookieContainer
to the handler, even though you say that it doesn't.
Setting the CookieContainer on the request didn't fix the problem on iOS MAUI, neither did not setting Options.CookieContainer
static void ConfigureHttpMessageHandler(HttpClientHandler handler, RestClientOptions options) {
#if NET
if (!OperatingSystem.IsBrowser()) {
#endif
handler.UseCookies = false;
handler.Credentials = options.Credentials;
handler.UseDefaultCredentials = options.UseDefaultCredentials;
handler.AutomaticDecompression = options.AutomaticDecompression;
handler.PreAuthenticate = options.PreAuthenticate;
if (options.MaxRedirects.HasValue) handler.MaxAutomaticRedirections = options.MaxRedirects.Value;
if (options.RemoteCertificateValidationCallback != null)
handler.ServerCertificateCustomValidationCallback =
(request, cert, chain, errors) => options.RemoteCertificateValidationCallback(request, cert, chain, errors);
if (options.ClientCertificates != null) {
handler.ClientCertificates.AddRange(options.ClientCertificates);
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}
It's weird that the issue only affects one particular platform. RestSharp doesn't do any kind of magic with cookies, and the reason why it doesn't set the cookie container property of the handler is because otherwise handling cookies will deviate from what's already there when the container isn't used.
Basically, after making a call, RestSharp tries to find cookie headers, collect the values, and add cookies to the response and to the Options.CookieContainer
. It's really strange that it doesn't work on iOS.
// Parse all the cookies from the response and update the cookie jar with cookies
if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) {
// ReSharper disable once PossibleMultipleEnumeration
cookieContainer.AddCookies(url, cookiesHeader);
// ReSharper disable once PossibleMultipleEnumeration
Options.CookieContainer?.AddCookies(url, cookiesHeader);
}
@alexeyzimarev Thank you for the suggestion. I tried to make the request on sample code without initializing the cookie container, but it did not work. In fact, after looking into the response object and comparing the Android and iOS, I realized that on iOS the ResstSharp.RestResponse
object has 9 headers and Android has 17 headers after the call completes. Strangely enough, on iOS there are no headers for Set-Cookie
, it seems the cookie container is not the main problem after all.
When using rest sharp in NET MAUI application development, for the same API call that is supposed to inject a cookie, in Android you will see the cookie but it is missing when running in iOS device.
Code Sample:
Expected behaviour: The response cookie should be added inside the cookie container.