java.net.SocketException when cancelling http request

Viewed 24

I'm downloading a file as a stream with httpClient.SendAsync() and on cancellation with CancellationToken I get a java.net.SocketException: Socket closed exception every time. Full stack trace:

[monodroid] Not wrapping exception of type Java.Net.SocketException from method `SendAsync`. This will change in a future release.
[0:]   at Java.Interop.JniEnvironment+InstanceMethods.CallIntMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00068] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniEnvironment.g.cs:11725 
  at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualInt32Method (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x00024] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:229 
  at Java.Net.HttpURLConnection.get_ResponseCode () [0x00000] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-31/mcw/Java.Net.HttpURLConnection.cs:511 
  at Xamarin.Android.Net.AndroidClientHandler+<>c__DisplayClass46_0.<DoProcessRequest>b__2 () [0x00000] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs:438 
  at System.Threading.Tasks.Task`1[TResult].InnerInvoke () [0x0000f] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs:534 
  at System.Threading.Tasks.Task.Execute () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:2319 
--- End of stack trace from previous location where exception was thrown ---

  at Xamarin.Android.Net.AndroidClientHandler.DoProcessRequest (System.Net.Http.HttpRequestMessage request, Java.Net.URL javaUrl, Java.Net.HttpURLConnection httpConnection, System.Threading.CancellationToken cancellationToken, Xamarin.Android.Net.AndroidClientHandler+RequestRedirectionState redirectState) [0x00328] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs:438 
  at Xamarin.Android.Net.AndroidClientHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00286] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs:287 
  at System.Net.Http.HttpClient.FinishSendAsyncBuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) [0x0017e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Net.Http/src/System/Net/Http/HttpClient.cs:506 
  at TestHttpClientSocketException.MainPage.startButton_Clicked (System.Object sender, System.EventArgs e) [0x00113] in C:\Projects\TestHttpClientSocketException\TestHttpClientSocketException\TestHttpClientSocketException\MainPage.xaml.cs:43 
  --- End of managed Java.Net.SocketException stack trace ---
java.net.SocketException: Socket closed
    at java.net.SocketInputStream.read(SocketInputStream.java:203)
    at java.net.SocketInputStream.read(SocketInputStream.java:139)
    at com.android.okhttp.okio.Okio$2.read(Okio.java:141)
    at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:211)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:316)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:310)
    at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:206)
    at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:395)
    at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:144)
    at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:887)
    at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:759)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:499)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:435)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:566)

If it is a OperationCanceledException or something like that then everything would be ok, but I don't open or close a socket manually. It's done by the underlying libraries (I use Android for the HttpClient implementation). In the following a minimal example can be found.

XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TestHttpClientSocketException.MainPage">

    <StackLayout>
        <Button x:Name="startButton" Text="Download" Clicked="startButton_Clicked" />
        <Button x:Name="cancelButton" Text="Cancel" Clicked="cancelButton_Clicked" />
    </StackLayout>

</ContentPage>

Code behind:

public partial class MainPage : ContentPage
{
    CancellationTokenSource cancelToken;

    public MainPage()
    {
        InitializeComponent();
        this.cancelToken = new CancellationTokenSource();
    }

    private async void startButton_Clicked(object sender, EventArgs e)
    {
        HttpResponseMessage response;

        try
        {
            using (var httpClient = new HttpClient())
            {
                HttpMethod method = HttpMethod.Post;
                string requestUri = "http://0.0.0.0:80/some/uri";
                httpClient.Timeout = new TimeSpan(0, 0, 300);
                HttpContent httpContent = new StringContent("<soapenv:Envelope ...");
                HttpRequestMessage req = new HttpRequestMessage(method, requestUri);
                req.Headers.Add("SOAPAction", "http://schemas.xmlsoap.org/soap/envelope");

                req.Method = method;
                req.Content = httpContent;
                req.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/xml" + "; charset=utf-8");
                response = await httpClient.SendAsync(req, this.cancelToken.Token).ConfigureAwait(false);
                
                System.Diagnostics.Debug.WriteLine("finished");
            }
        } catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.StackTrace);
        }
        
    }

    private void cancelButton_Clicked(object sender, EventArgs e)
    {
        this.cancelToken.Cancel();
        this.cancelToken = new CancellationTokenSource();
    }
}

Why I'm getting a SocketException and how should I handle this? Should I ignore it? Am I doing something wrong?

1 Answers

First, you can create a static object in the App.xaml.cs like this:

public static object HttpClientHandler;

Then, open the MainActivity.cs to initialize CustomAndroidClientHandler in your OnCreate method, put it before the LoadApplication(new App()); line:

protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
        var androidClientHandler = new CustomAndroidClientHandler();
        App.HttpClientHandler = androidClientHandler;
        LoadApplication(new App());
    }

Finally, you can use App.HttpClientHandler in your shared code when you new HttpClient().

new HttpClient((HttpMessageHandler)App.HttpClientHandler)
        {
        };
Related