From 698819fdb25110bb286a103e6ba69af9d53fbc6c Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 22 Nov 2021 09:54:22 +0100 Subject: [PATCH 1/2] Allow zero byte reads on raw HTTP/1.1 response streams --- .../System/Net/Http/SocketsHttpHandler/HttpConnection.cs | 2 +- .../Net/Http/SocketsHttpHandler/RawConnectionStream.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs index cff642bcb5eed..b4bc1d8a6c1cb 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs @@ -1749,7 +1749,7 @@ private ValueTask ReadBufferedAsync(Memory destination) // If the caller provided buffer, and thus the amount of data desired to be read, // is larger than the internal buffer, there's no point going through the internal // buffer, so just do an unbuffered read. - return destination.Length >= _readBuffer.Length ? + return destination.Length == 0 || destination.Length >= _readBuffer.Length ? ReadAsync(destination) : ReadBufferedAsyncCore(destination); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs index 44376b01a95b2..0255adc776f4d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs @@ -45,9 +45,9 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation CancellationHelper.ThrowIfCancellationRequested(cancellationToken); HttpConnection? connection = _connection; - if (connection == null || buffer.Length == 0) + if (connection == null) { - // Response body fully consumed or the caller didn't ask for any data + // Response body fully consumed return 0; } @@ -74,7 +74,7 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation } } - if (bytesRead == 0) + if (bytesRead == 0 && buffer.Length != 0) { // A cancellation request may have caused the EOF. CancellationHelper.ThrowIfCancellationRequested(cancellationToken); From 23da174fe505fe0854f99438df4301dab99e8553 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 25 Jul 2022 19:45:40 +0200 Subject: [PATCH 2/2] Add test --- .../FunctionalTests/SocketsHttpHandlerTest.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index ccdd789e1cc27..a4e810aa6047a 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -1329,6 +1329,39 @@ await server.AcceptConnectionAsync(async (LoopbackServer.Connection connection) } }); } + + [Fact] + public async Task UpgradeConnection_ResponseStreamSupportsZeroByteReads() + { + var zeroByteReadIssued = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using HttpClient client = CreateHttpClient(); + + using HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + using Stream responseStream = await response.Content.ReadAsStreamAsync(); + + ValueTask zeroByteReadTask = responseStream.ReadAsync(Memory.Empty); + + Assert.False(zeroByteReadTask.IsCompleted); + await Task.Delay(10); + Assert.False(zeroByteReadTask.IsCompleted); + + zeroByteReadIssued.SetResult(); + + Assert.Equal(0, await zeroByteReadTask); + Assert.Equal(1, await responseStream.ReadAsync(new byte[2])); + }, + server => server.AcceptConnectionAsync(async connection => + { + await connection.ReadRequestHeaderAndSendCustomResponseAsync($"HTTP/1.1 101 Switching Protocols\r\nDate: {DateTimeOffset.UtcNow:R}\r\n\r\n"); + + await zeroByteReadIssued.Task; + + await connection.Stream.WriteAsync(new byte[] { (byte)'A' }); + })); + } } [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]