anaisbetts / ModernHttpClient

HttpClient implementations that use platform-native HTTP clients for :rocket:
MIT License
659 stars 261 forks source link

Aborting reading from chunked stream does not work #224

Open ssteiner opened 8 years ago

ssteiner commented 8 years ago

Hi

I'm using ModernHttpClient to connect to a server that serves chunked responses. To read and process the individual streams, I'm using this code (error handling stripped for readability)

        HttpClient `longPollClient = new HttpClient(new ModernHttpClient.NativeMessageHandler());
        longPollClient.BaseAddress = client.BaseAddress;
        longPollClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
        longPollClient.DefaultRequestHeaders.TransferEncodingChunked = true;

        MobileOperationParameters parameters = new MobileOperationParameters{ SessionId = currentSessionId };
        HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, "PollEvents");
        string address = string.Format("{0}{1}", longPollClient.BaseAddress, "PollEvents");
        req.Content = new StringContent(JsonConvert.SerializeObject(parameters));
        req.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
        req.Content.Headers.ContentType.CharSet = "utf-8";
        writeLog("sending polling request to " + address, 4);
        try
        {
            Task<HttpResponseMessage> getTask = longPollClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
            getTask.Wait(cts.Token);
            if (getTask.IsCompleted)
            {
                HttpResponseMessage response = getTask.Result;
                if (response.IsSuccessStatusCode)
                {
                    Task<System.IO.Stream> readTask = response.Content.ReadAsStreamAsync();
                    readTask.Wait();
                    if (readTask.IsCompleted)
                    {
                        chunkedStream = readTask.Result;
                        startReadingFromChunkedStream(chunkedStream);
                        return true;
                    }

        }`

And here's the reading from the chunked stream

`    private void startReadingFromChunkedStream(System.IO.Stream chunkedStream)
    {
        Task readerTask = Task.Factory.StartNew(() => readFromChunkedStream(chunkedStream), cts.Token);
        readerTask.ContinueWith(t =>
        {
            foreach (var e in t.Exception.Flatten().InnerExceptions)
                writeLog("Exception reading from chunked stream: " + e.Message, 2);
        }, TaskContinuationOptions.OnlyOnFaulted);
        readerTask.ContinueWith(t =>
        {
            writeLog("Chunked stream reader task cancelled", 3);
        }, TaskContinuationOptions.OnlyOnCanceled);
    }

    private void readFromChunkedStream(System.IO.Stream chunkedStream)
    {
        DateTime requestStartedAt = DateTime.Now;
        try
        {
            if (chunkedStream.CanTimeout)
                chunkedStream.ReadTimeout = Timeout.Infinite;
            ignoreChunkedChannelDisconnect = false;
            using (System.IO.StreamReader sr = new System.IO.StreamReader(chunkedStream))
            {
                string line = null;
                while ((line = sr.ReadLine()) != null)
                {
                    if (cts.IsCancellationRequested)
                    {
                        writeLog("stop polling requested, aborting polling task", 4);
                        break;
                    }
                    if (EventDataReceived != null)
                        EventDataReceived(line); // go back to BeginInvoke for more efficient processing
                        //EventDataReceived.BeginInvoke(line, EventDataReceived.EndInvoke, null);
                }
            }
        }
        catch (Exception e)
        {}
      }

`

When the connection is no longer needed I abort reading using this code

`longPollClient.CancelPendingRequests();
        if (chunkedStream != null)
        {
            ignoreChunkedChannelDisconnect = true;
            try
            {
                chunkedStream.Close();
            }
            catch (Exception)
            {

            }
            chunkedStream = null;
        }
        try
        {
            cts.Cancel();
        }
        catch (AggregateException aex)
        {
        }
    }`

calling cancel on cts should trigger the exception handling in the readFromChunkedStream method. That works fine on PC, but on Android, it seems not to do anything and the stream stays connected forever.

For repro, I have this PC sample of a server and client that is ready to go: https://github.com/ssteiner/ChunkedStreamTest

How can I abort reading from the chunked stream on android? (I'm using a Nexus 6P fully patched by the way but customers also have the issue on other devices).