dotnet / runtime

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

AndroidMessageHandler or HttpClientHandler throw System.ObjectDisposedException: Cannot access a closed Stream #95658

Closed PontiacGTX closed 10 months ago

PontiacGTX commented 2 years ago

Description

I am trying to do a local http post request from a http client but whenever I tried overriding to check the signed certificate by giving a HttpClientHandler or AndroidMessageHandler ot the http client, it ends up throwing an exception when I call to PostAsync method

Steps to Reproduce

Create a Maui Project On Program Add a HttpClient to the DI container

#if DEBUG
         var httpHelper = new HttpClientHandlerHelper();

         var insecureHandler =new CustomAndroidMessageHandler();//httpHelper.GetInsecureHandler()
        builder.Services.AddSingleton<HttpClient>(x=> new HttpClient(httpHelper.GetInsecureHandler(), false));

#else
        builder.Services.AddSingleton<HttpClient>();
#endif

declare a class to get the http client handler

HttpClientHandlerHelper

  public class HttpClientHandlerHelper:IDisposable
    {
        private bool disposedValue;

        public HttpClientHandler GetInsecureHandler()
        {
            HttpClientHandler handler = new HttpClientHandler();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
            {

                return true;
            };
            return handler;
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: eliminar el estado administrado (objetos administrados)
                }

                // TODO: liberar los recursos no administrados (objetos no administrados) y reemplazar el finalizador
                // TODO: establecer los campos grandes como NULL
                disposedValue = true;
            }
        }

        public void Dispose()
        {
            // No cambie este código. Coloque el código de limpieza en el método "Dispose(bool disposing)".
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }

make some service using this HttpClient by DI and then call a method using PostAsync, but in the url the base url should contain the local ip in my case is like, while calling a webapi running

http://192.168.250.3:5000/api/Account/Create

Version with bug

6.0 Release Candidate 2 or older

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

API 30 Android 11, WIndows 10 1709

Did you find any workaround?

Not yet

Relevant log output

System.ObjectDisposedException: Cannot access a closed Stream.
   at System.IO.MemoryStream.EnsureNotClosed()
   at System.IO.MemoryStream.get_Length()
   at System.Net.Http.HttpContent.GetComputedOrBufferLength()
   at System.Net.Http.Headers.HttpContentHeaders.get_ContentLength()
   at Xamarin.Android.Net.AndroidMessageHandler.SetupRequestBody(HttpURLConnection httpConnection, HttpRequestMessage request) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 1132
   at Xamarin.Android.Net.AndroidMessageHandler.SetupRequestInternal(HttpRequestMessage request, URLConnection conn) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 984
   at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 374
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at MauiChat.Common.Services.ChatServicesService.CreateUser(String phoneNumber) in C:\Users\PontiacGTX\Source\Repos\MauiChat\Common\Services\ChatServicesService.cs:line 38}
PureWeen commented 2 years ago

Since you're registering as a singleton I'm guessing you're disposing of it and it keeps returning the disposed instance but need a sample to be sure.

PontiacGTX commented 2 years ago

Since you're registering as a singleton I'm guessing you're disposing of it and it keeps returning the disposed instance but need a sample to be sure.

ok try this repo https://github.com/PontiacGTX/MauiChat/tree/PontiacGTX-BRANCH

PontiacGTX commented 2 years ago

Since you're registering as a singleton I'm guessing you're disposing of it and it keeps returning the disposed instance but need a sample to be sure.

I have looking and I think if I could pass my own HttpMessageInvoker I could assign it to not be disposed https://github.com/dotnet/runtime/blob/4ff5a3a85a9b0de7ab9e9267959e668142815f4f/src/libraries/System.Net.Http/src/System/Net/Http/HttpMessageInvoker.cs#L124

https://microsoft.github.io/reverse-proxy/articles/http-client-config.html

Edit: also I tried instancing a new httpclient witout DI and it keep saying that it couldnt read on a disposed Stream

hartez commented 2 years ago

@PontiacGTX If you configure the HttpClient using AddTransient instead of AddSingleton, does the problem persist?

ghost commented 2 years ago

Hi @PontiacGTX. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

PontiacGTX commented 2 years ago

@PontiacGTX If you configure the HttpClient using AddTransient instead of AddSingleton, does the problem persist?

same exception using AndroidMessageHandler, somewhere it is disposing it or not reading to the end of the stream correctly or I dont know what else to think also I see AndroidMessageHandler has 1012 lines and the repository has like 330s

is there a difference I am using this legacy file? https://github.com/xamarin/xamarin-android/blob/6290a9c63251e7bbbb3a40ab5bdd230688267b85/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs#L363

System.ObjectDisposedException: Cannot access a closed Stream. at System.IO.MemoryStream.EnsureNotClosed() at System.IO.MemoryStream.CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) at Xamarin.Android.Net.AndroidMessageHandler.WriteRequestContentToOutput(HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 458 at Xamarin.Android.Net.AndroidMessageHandler.DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 533 at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 375 at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at MauiChat.Common.HelperClass.HttpClientHelper.PostStringDataAsync(HttpClient httpClient, Object data, String url) in C:\Users\PontiacGTX\source\repos\MauiChat\Common\HelperClass\HttpClientHelper.cs:line 19 at MauiChat.Common.Services.ChatServicesService.CreateUser(String phoneNumber) in C:\Users\PontiacGTX\source\repos\MauiChat\Common\Services\ChatServicesService.cs:line 39}

hartez commented 2 years ago

@PontiacGTX does the endpoint you are posting to respond with a redirect?

ghost commented 2 years ago

Hi @PontiacGTX. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

PontiacGTX commented 2 years ago

@PontiacGTX does the endpoint you are posting to respond with a redirect?

no I have tried it with postman and returns Ok and then I see that it isnt being called with my debugger. I think IISExpress should be fine since I am doing a request from a local ip, curiously if the server has a timeout the the http client states it has time out but when reading the message it seems to have issues I will try again uninstalling my previous Android SDKs and see if that is the issue, or how can I use the latest version of AndroidClientHandler?

uninstalled all sdk except 30/31 android 11/12

and still the same exception using the default handler for android

System.ObjectDisposedException: Cannot access a closed Stream. at System.IO.MemoryStream.EnsureNotClosed() at System.IO.MemoryStream.CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) at Xamarin.Android.Net.AndroidMessageHandler.WriteRequestContentToOutput(HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 458 at Xamarin.Android.Net.AndroidMessageHandler.DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 533 at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 375 at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at MauiChat.Common.HelperClass.HttpClientHelper.PostStringDataAsync(HttpClient httpClient, Object data, String url) in C:\Users\PontiacGTX\source\repos\MauiChat\Common\HelperClass\HttpClientHelper.cs:line 19 at MauiChat.Common.Services.ChatServicesService.CreateUser(String phoneNumber) in C:\Users\PontiacGTX\source\repos\MauiChat\Common\Services\ChatServicesService.cs:line 39}

using

        {
            HttpClientHandler handler = new();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
            {
                Console.WriteLine(message);
                return true;
            };
            return handler;
        }

returns

System.Net.WebException: Hostname 192.168.250.3 not verified: certificate: sha1/fVGKMV1jTGIwfF6V4Tb79pfSZ8Q= DN: CN=localhost subjectAltNames: [localhost] ---> Javax.Net.Ssl.SSLPeerUnverifiedException: Hostname 192.168.250.3 not verified: certificate: sha1/fVGKMV1jTGIwfF6V4Tb79pfSZ8Q= DN: CN=localhost subjectAltNames: [localhost] at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniEnvironment.g.cs:line 11884 at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeAbstractVoidMethod(String encodedMember, IJavaPeerable self, JniArgumentValue parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 17 at Javax.Net.Ssl.HttpsURLConnectionInvoker.Connect() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net6.0/android-31/mcw/Javax.Net.Ssl.HttpsURLConnection.cs:line 433 at Xamarin.Android.Net.AndroidMessageHandler.<>c__DisplayClass125_0.b0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 450 at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.<>c.<.cctor>b272_0(Object obj) at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) --- End of stack trace from previous location --- at Xamarin.Android.Net.AndroidMessageHandler.DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 502 at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 375 --- End of managed Javax.Net.Ssl.SSLPeerUnverifiedException stack trace --- javax.net.ssl.SSLPeerUnverifiedException: Hostname 192.168.250.3 not verified: certificate: sha1/fVGKMV1jTGIwfF6V4Tb79pfSZ8Q= DN: CN=localhost subjectAltNames: [localhost] at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:205) at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:153) at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116) at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186) at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128) at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97) at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289) at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232) at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465) at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131) at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:90) at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:30)

--- End of managed Javax.Net.Ssl.SSLPeerUnverifiedException stack trace --- javax.net.ssl.SSLPeerUnverifiedException: Hostname 192.168.250.3 not verified: certificate: sha1/fVGKMV1jTGIwfF6V4Tb79pfSZ8Q= DN: CN=localhost subjectAltNames: [localhost] at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:205) at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:153) at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116) at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186) at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128) at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97) at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289) at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232) at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)

similar issue to https://github.com/dotnet/runtime/issues/70434

joebeernink commented 2 years ago

Also seeing this issue. Only affects POST calls with content and the HttpClient is injected, we see this. If we use the same HttpClient for a get, or a post with no content, it works fine.

However, if we create a brand new Http client immediately before the call, it's fine.

ghost commented 2 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

PontiacGTX commented 2 years ago

Also seeing this issue. Only affects POST calls with content and the HttpClient is injected, we see this. If we use the same HttpClient for a get, or a post with no content, it works fine.

However, if we create a brand new Http client immediately before the call, it's fine.

So what should I do is instancing a new http client before making a post?as a workaround?

joebeernink commented 2 years ago

This is what we had to do. It's not great, but at least it unblocked us.

public async Task AddAttendeeAsync(EventAttendee eventAttendee, CancellationToken cancellationToken = default) { try { var content = JsonContent.Create(eventAttendee, typeof(EventAttendee), null, SerializerOptions);

           var authorizedHttpClient = HttpClientService.CreateAuthorizedClient();
            var httpClient = new HttpClient();
            httpClient.BaseAddress = authorizedHttpClient.BaseAddress;

           using (var response = await httpClient.PostAsync(EventAttendeeApi, content, cancellationToken))
            {
                response.EnsureSuccessStatusCode();
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(@"\tERROR {0}", ex.Message);
            throw;
        }
    }
thinkOfaNumber commented 1 year ago

I suspect I have the same problem as the OP, given I'm also using a CustomAndroidMessageHandler to override the hostname verification and certificate, and I'm also using an IP address and HTTP http://192.168.2.127:5241/api/v1/Login.

HttpClient throws the same exception if my server redirects http to https, but if I turn that off without touching the MAUI app, it works.

I am injecting HttpClient as a singleton, using AddTransient makes no difference to the exception:

builder.Services
    //... other services ...
    .AddSingleton<HttpsClientHandlerService>()
    .AddSingleton<HttpClient>(provider =>
    {
        var handler = provider.GetService<HttpsClientHandlerService>();
        if (handler == null)
        {
            return new HttpClient();
        }
        else
        {
            return new HttpClient(handler.GetPlatformMessageHandler());
        }
    })

HttpsClientHandlerService handles both hostname and certificate:

public class HttpsClientHandlerService
{
    public HttpMessageHandler GetPlatformMessageHandler()
    {
#if ANDROID
        var handler = new CustomAndroidMessageHandler();
        handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
        {
            if (cert != null && cert.Issuer.Equals("CN=localhost"))
                return true;
            return errors == System.Net.Security.SslPolicyErrors.None;
        };
        return handler;
#elif IOS
        var handler = new NSUrlSessionHandler
        {
            TrustOverrideForUrl = IsHttpsLocalhost
        };
        return handler;
#else
     throw new PlatformNotSupportedException("Only Android and iOS supported.");
#endif
    }

#if ANDROID
    internal sealed class CustomAndroidMessageHandler : Xamarin.Android.Net.AndroidMessageHandler
    {
        protected override Javax.Net.Ssl.IHostnameVerifier GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection connection)
            => new CustomHostnameVerifier();

        private sealed class CustomHostnameVerifier : Java.Lang.Object, Javax.Net.Ssl.IHostnameVerifier
        {
            public bool Verify(string hostname, Javax.Net.Ssl.ISSLSession session)
            {
                return Javax.Net.Ssl.HttpsURLConnection.DefaultHostnameVerifier.Verify(hostname, session) ||
                    ((hostname == "10.0.2.2" || hostname == "192.168.2.127") && session.PeerPrincipal?.Name == "CN=localhost");
            }
        }
    }
#elif IOS
    public bool IsHttpsLocalhost(NSUrlSessionHandler sender, string url, Security.SecTrust trust)
    {
        if (url.StartsWith("https://localhost"))
            return true;
        return false;
    }
#endif
}

The server is a .NET Core 6 api using

var app = builder.Build();
// ...
app.UseHttpsRedirection(); //

commenting out that last app.UseHttpsRedirection(); on the server makes the MAUI client connect without exception.

PontiacGTX commented 1 year ago

@hartez @joebeernink @thinkOfaNumber @PureWeen

So I had actually followed the solution that I had to create a http client based off a message handler either for HttpClient or an Android Handler, but the problem was that the service where the HttpClient was being injected was a Transient one, and the HttpClient was a Singleton, so I fixed the issue by using a Scoped service then to inject a HttpClient as Singleton , so the HttpClient´s Message hander is being Disposed before the response has been read, so I wonder if this would be a bug for the DI container or maybe a misuse from my part?

arahmancsd commented 1 year ago

I am facing the same issue in .net8 rc1 without using the DI.

builder.Services.AddSingleton<IFileDownloadService, FileDownloadService>();

public class FileDownloadService : IFileDownloadService
{
private readonly HttpClient client;

public FileDownloadService()
{
//DEBUG
    var httpClientHandler = new HttpClientHandler();
    httpClientHandler.ServerCertificateCustomValidationCallback += (sender, certificate, chain, errors) => true;
    client = new(httpClientHandler);
}

public async Task<FileDownloadResponse> DownloadFileAsync(){..}
}

The only way it works in .net 8 is to define the httpClient in the DownloadFileAsync function instead of the constructor.

public async Task<FileDownloadResponse> DownloadFileAsync()
{
    var httpClientHandler = new HttpClientHandler();
    httpClientHandler.ServerCertificateCustomValidationCallback += (sender, certificate, chain, errors) => true;
    client = new(httpClientHandler);
    ....
}

Switching back to net7 solves the issue. I guess this is something with .net8 rc.

ghost commented 10 months ago

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

Issue Details
### Description I am trying to do a local http post request from a http client but whenever I tried overriding to check the signed certificate by giving a HttpClientHandler or AndroidMessageHandler ot the http client, it ends up throwing an exception when I call to PostAsync method ### Steps to Reproduce Create a Maui Project On Program Add a HttpClient to the DI container ```c# #if DEBUG var httpHelper = new HttpClientHandlerHelper(); var insecureHandler =new CustomAndroidMessageHandler();//httpHelper.GetInsecureHandler() builder.Services.AddSingleton(x=> new HttpClient(httpHelper.GetInsecureHandler(), false)); #else builder.Services.AddSingleton(); #endif declare a class to get the http client handler HttpClientHandlerHelper public class HttpClientHandlerHelper:IDisposable { private bool disposedValue; public HttpClientHandler GetInsecureHandler() { HttpClientHandler handler = new HttpClientHandler(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }; return handler; } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: eliminar el estado administrado (objetos administrados) } // TODO: liberar los recursos no administrados (objetos no administrados) y reemplazar el finalizador // TODO: establecer los campos grandes como NULL disposedValue = true; } } public void Dispose() { // No cambie este código. Coloque el código de limpieza en el método "Dispose(bool disposing)". Dispose(disposing: true); GC.SuppressFinalize(this); } } ``` make some service using this HttpClient by DI and then call a method using PostAsync, but in the url the base url should contain the local ip in my case is like, while calling a webapi running http://192.168.250.3:5000/api/Account/Create ### Version with bug 6.0 Release Candidate 2 or older ### Last version that worked well Unknown/Other ### Affected platforms Android ### Affected platform versions API 30 Android 11, WIndows 10 1709 ### Did you find any workaround? Not yet ### Relevant log output ```shell System.ObjectDisposedException: Cannot access a closed Stream. at System.IO.MemoryStream.EnsureNotClosed() at System.IO.MemoryStream.get_Length() at System.Net.Http.HttpContent.GetComputedOrBufferLength() at System.Net.Http.Headers.HttpContentHeaders.get_ContentLength() at Xamarin.Android.Net.AndroidMessageHandler.SetupRequestBody(HttpURLConnection httpConnection, HttpRequestMessage request) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 1132 at Xamarin.Android.Net.AndroidMessageHandler.SetupRequestInternal(HttpRequestMessage request, URLConnection conn) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 984 at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 374 at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at MauiChat.Common.Services.ChatServicesService.CreateUser(String phoneNumber) in C:\Users\PontiacGTX\Source\Repos\MauiChat\Common\Services\ChatServicesService.cs:line 38} ```
Author: PontiacGTX
Assignees: -
Labels: `area-System.Net.Http`, `untriaged`
Milestone: -
ghost commented 10 months ago

Tagging subscribers to 'arch-android': @steveisok, @akoeplinger See info in area-owners.md if you want to be subscribed.

Issue Details
### Description I am trying to do a local http post request from a http client but whenever I tried overriding to check the signed certificate by giving a HttpClientHandler or AndroidMessageHandler ot the http client, it ends up throwing an exception when I call to PostAsync method ### Steps to Reproduce Create a Maui Project On Program Add a HttpClient to the DI container ```c# #if DEBUG var httpHelper = new HttpClientHandlerHelper(); var insecureHandler =new CustomAndroidMessageHandler();//httpHelper.GetInsecureHandler() builder.Services.AddSingleton(x=> new HttpClient(httpHelper.GetInsecureHandler(), false)); #else builder.Services.AddSingleton(); #endif declare a class to get the http client handler HttpClientHandlerHelper public class HttpClientHandlerHelper:IDisposable { private bool disposedValue; public HttpClientHandler GetInsecureHandler() { HttpClientHandler handler = new HttpClientHandler(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }; return handler; } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: eliminar el estado administrado (objetos administrados) } // TODO: liberar los recursos no administrados (objetos no administrados) y reemplazar el finalizador // TODO: establecer los campos grandes como NULL disposedValue = true; } } public void Dispose() { // No cambie este código. Coloque el código de limpieza en el método "Dispose(bool disposing)". Dispose(disposing: true); GC.SuppressFinalize(this); } } ``` make some service using this HttpClient by DI and then call a method using PostAsync, but in the url the base url should contain the local ip in my case is like, while calling a webapi running http://192.168.250.3:5000/api/Account/Create ### Version with bug 6.0 Release Candidate 2 or older ### Last version that worked well Unknown/Other ### Affected platforms Android ### Affected platform versions API 30 Android 11, WIndows 10 1709 ### Did you find any workaround? Not yet ### Relevant log output ```shell System.ObjectDisposedException: Cannot access a closed Stream. at System.IO.MemoryStream.EnsureNotClosed() at System.IO.MemoryStream.get_Length() at System.Net.Http.HttpContent.GetComputedOrBufferLength() at System.Net.Http.Headers.HttpContentHeaders.get_ContentLength() at Xamarin.Android.Net.AndroidMessageHandler.SetupRequestBody(HttpURLConnection httpConnection, HttpRequestMessage request) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 1132 at Xamarin.Android.Net.AndroidMessageHandler.SetupRequestInternal(HttpRequestMessage request, URLConnection conn) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 984 at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 374 at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at MauiChat.Common.Services.ChatServicesService.CreateUser(String phoneNumber) in C:\Users\PontiacGTX\Source\Repos\MauiChat\Common\Services\ChatServicesService.cs:line 38} ```
Author: PontiacGTX
Assignees: -
Labels: `area-System.Net.Http`, `os-android`, `untriaged`
Milestone: -
wfurt commented 10 months ago

AFAIK HttpClientHandler will use platform handler if you target Maui. You would need to use SocketsHttphandler explicitly if you want to use the managed implementation.

cc: @simonrozsival for more comments.

simonrozsival commented 10 months ago

@wfurt Yes, the native handler is used by default (in case of Android that is AndroidMessageHandler). It's possible to either create SocketsHttpHandler manually and pass it to HttpClient or the developer can add <UseNativeHttpHandler>false</UseNativeHttpHandler> to change the default behavior.

The original issue used ServerCertificateCustomValidationCallback in .NET 6. At that time this functionality wasn't properly implemented in either the native nor the managed HTTP handlers. The native handler supports the validation callback since .NET 7 and the managed one since .NET 8.

@arahmancsd I'm surprised there is a difference between .NET 7 and .NET 8 RC1. Have you tried again with the final release of .NET 8? Can you still reproduce the issue?