Get rid of StreamWriter usage in HTTP loopback server and fix HTTP/1.1 loopback imple...
authorGeoff Kizer <geoffrek@microsoft.com>
Tue, 26 Jan 2021 00:30:29 +0000 (16:30 -0800)
committerGitHub <noreply@github.com>
Tue, 26 Jan 2021 00:30:29 +0000 (16:30 -0800)
Get rid of StreamWriter usage in HTTP loopback server and fix HTTP/1.1 loopback implementation of SendResponseBodyAsync

Co-authored-by: Geoffrey Kizer <geoffrek@windows.microsoft.com>
15 files changed:
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Asynchrony.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cancellation.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs
src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Connect.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
src/libraries/System.Net.WebSockets.Client/tests/LoopbackHelper.cs

index 44c4a39..6834f83 100644 (file)
@@ -59,7 +59,7 @@ namespace System.Net.Http.Functional.Tests
                     await server.AcceptConnectionAsync(async connection =>
                     {
                         await connection.ReadRequestHeaderAsync();
-                        await connection.Writer.WriteAsync(
+                        await connection.WriteStringAsync(
                             LoopbackServer.GetContentModeResponse(
                                 contentMode,
                                 string.Concat(Enumerable.Repeat('s', 10_000)),
index 72c02b6..d246ca5 100644 (file)
@@ -351,10 +351,10 @@ namespace System.Net.Http.Functional.Tests
                     Task serverTask1 = server.AcceptConnectionAsync(async connection1 =>
                     {
                         await connection1.ReadRequestHeaderAsync();
-                        await connection1.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\n");
+                        await connection1.WriteStringAsync($"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\n");
                         serverAboutToBlock.SetResult(true);
                         await blockServerResponse.Task;
-                        await connection1.Writer.WriteAsync("Content-Length: 5\r\n\r\nhello");
+                        await connection1.WriteStringAsync("Content-Length: 5\r\n\r\nhello");
                     });
 
                     Task get1 = client.GetAsync(url);
index ae0384d..fe808e7 100644 (file)
@@ -101,7 +101,7 @@ namespace System.Net.Http.Functional.Tests
                 await server.AcceptConnectionAsync(async connection =>
                 {
                     await connection.ReadRequestHeaderAsync();
-                    await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
+                    await connection.WriteStringAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
                     using (Stream compressedStream = compress(connection.Stream))
                     {
                         await compressedStream.WriteAsync(expectedContent);
@@ -163,7 +163,7 @@ namespace System.Net.Http.Functional.Tests
                 await server.AcceptConnectionAsync(async connection =>
                 {
                     await connection.ReadRequestHeaderAsync();
-                    await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
+                    await connection.WriteStringAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
                     await connection.Stream.WriteAsync(compressedContent);
                 });
             });
index 3117f52..2c834e9 100644 (file)
@@ -86,7 +86,7 @@ namespace System.Net.Http.Functional.Tests
                             {
                                 while (!cts.IsCancellationRequested)
                                 {
-                                    await connection.Writer.WriteAsync(new string('s', 16000));
+                                    await connection.WriteStringAsync(new string('s', 16000));
                                     await Task.Delay(1);
                                 }
                             }
index 2cb88d3..885a202 100644 (file)
@@ -969,12 +969,11 @@ namespace System.Net.Http.Functional.Tests
                     Task serverTask = server.AcceptConnectionAsync(async connection =>
                     {
                         await connection.ReadRequestHeaderAndSendCustomResponseAsync("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
-                        TextWriter writer = connection.Writer;
                         try
                         {
                             while (!cts.IsCancellationRequested) // infinite to make sure implementation doesn't OOM
                             {
-                                await writer.WriteAsync(new string(' ', 10000));
+                                await connection.WriteStringAsync(new string(' ', 10000));
                                 await Task.Delay(1);
                             }
                         }
index 4eb602c..ec7bb19 100644 (file)
@@ -442,18 +442,18 @@ namespace System.Net.Http.Functional.Tests
                 {
                     await connection.ReadRequestHeaderAsync();
 
-                    await connection.Writer.WriteAsync($"HTTP/1.1 200 OK{lineEnding}Transfer-Encoding: chunked{lineEnding}{lineEnding}");
+                    await connection.WriteStringAsync($"HTTP/1.1 200 OK{lineEnding}Transfer-Encoding: chunked{lineEnding}{lineEnding}");
                     for (int bytesSent = 0; bytesSent < expectedData.Length;)
                     {
                         int bytesRemaining = expectedData.Length - bytesSent;
                         int bytesToSend = rand.Next(1, Math.Min(bytesRemaining, maxChunkSize + 1));
-                        await connection.Writer.WriteAsync(bytesToSend.ToString("X") + lineEnding);
+                        await connection.WriteStringAsync(bytesToSend.ToString("X") + lineEnding);
                         await connection.Stream.WriteAsync(new Memory<byte>(expectedData, bytesSent, bytesToSend));
-                        await connection.Writer.WriteAsync(lineEnding);
+                        await connection.WriteStringAsync(lineEnding);
                         bytesSent += bytesToSend;
                     }
-                    await connection.Writer.WriteAsync($"0{lineEnding}");
-                    await connection.Writer.WriteAsync(lineEnding);
+                    await connection.WriteStringAsync($"0{lineEnding}");
+                    await connection.WriteStringAsync(lineEnding);
                 });
             });
         }
index 845bd19..b44ae2d 100644 (file)
@@ -397,7 +397,6 @@ namespace System.Net.Test.Common
             private const int BufferSize = 4000;
             private Socket _socket;
             private Stream _stream;
-            private StreamWriter _writer;
             private byte[] _readBuffer;
             private int _readStart;
             private int _readEnd;
@@ -409,8 +408,6 @@ namespace System.Net.Test.Common
                 _socket = socket;
                 _stream = stream;
 
-                _writer = new StreamWriter(stream, Encoding.ASCII) { AutoFlush = true };
-
                 _readBuffer = new byte[BufferSize];
                 _readStart = 0;
                 _readEnd = 0;
@@ -418,7 +415,6 @@ namespace System.Net.Test.Common
 
             public Socket Socket => _socket;
             public Stream Stream => _stream;
-            public StreamWriter Writer => _writer;
 
             public static async Task<Connection> CreateAsync(Socket socket, Stream stream, Options httpOptions)
             {
@@ -620,7 +616,6 @@ namespace System.Net.Test.Common
                 }
                 catch (Exception) { }
 
-                _writer.Dispose();
                 _stream.Dispose();
                 _socket?.Dispose();
             }
@@ -673,9 +668,20 @@ namespace System.Net.Test.Common
                 return lines;
             }
 
+            public async Task WriteStringAsync(string s)
+            {
+                byte[] bytes = Encoding.ASCII.GetBytes(s);
+                await _stream.WriteAsync(bytes);
+            }
+
             public async Task SendResponseAsync(string response)
             {
-                await _writer.WriteAsync(response).ConfigureAwait(false);
+                await WriteStringAsync(response);
+            }
+
+            public async Task SendResponseAsync(byte[] response)
+            {
+                await _stream.WriteAsync(response);
             }
 
             public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, string additionalHeaders = null, string content = null)
@@ -686,7 +692,7 @@ namespace System.Net.Test.Common
             public async Task<List<string>> ReadRequestHeaderAndSendCustomResponseAsync(string response)
             {
                 List<string> lines = await ReadRequestHeaderAsync().ConfigureAwait(false);
-                await _writer.WriteAsync(response).ConfigureAwait(false);
+                await WriteStringAsync(response);
                 return lines;
             }
 
@@ -892,7 +898,7 @@ namespace System.Net.Test.Common
 
             public override async Task SendResponseBodyAsync(byte[] body, bool isFinal = true, int requestId = 0)
             {
-                await SendResponseAsync(Encoding.UTF8.GetString(body)).ConfigureAwait(false);
+                await SendResponseAsync(body).ConfigureAwait(false);
             }
 
             public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
index 1851e0b..3e0e30e 100644 (file)
@@ -321,15 +321,14 @@ namespace System.Net.Http.Functional.Tests
                     }
 
                     // Write response header
-                    TextWriter writer = connection.Writer;
-                    await writer.WriteAsync("HTTP/1.1 200 OK\r\n").ConfigureAwait(false);
-                    await writer.WriteAsync($"Date: {DateTimeOffset.UtcNow:R}\r\n").ConfigureAwait(false);
-                    await writer.WriteAsync("Content-Type: text/plain\r\n").ConfigureAwait(false);
+                    await connection.WriteStringAsync("HTTP/1.1 200 OK\r\n").ConfigureAwait(false);
+                    await connection.WriteStringAsync($"Date: {DateTimeOffset.UtcNow:R}\r\n").ConfigureAwait(false);
+                    await connection.WriteStringAsync("Content-Type: text/plain\r\n").ConfigureAwait(false);
                     if (!string.IsNullOrEmpty(transferHeader))
                     {
-                        await writer.WriteAsync(transferHeader).ConfigureAwait(false);
+                        await connection.WriteStringAsync(transferHeader).ConfigureAwait(false);
                     }
-                    await writer.WriteAsync("\r\n").ConfigureAwait(false);
+                    await connection.WriteStringAsync("\r\n").ConfigureAwait(false);
 
                     // Write response body
                     if (transferType == TransferType.Chunked)
@@ -337,16 +336,16 @@ namespace System.Net.Http.Functional.Tests
                         string chunkSizeInHex = string.Format(
                             "{0:x}\r\n",
                             content.Length + (transferError == TransferError.ChunkSizeTooLarge ? 42 : 0));
-                        await writer.WriteAsync(chunkSizeInHex).ConfigureAwait(false);
-                        await writer.WriteAsync($"{content}\r\n").ConfigureAwait(false);
+                        await connection.WriteStringAsync(chunkSizeInHex).ConfigureAwait(false);
+                        await connection.WriteStringAsync($"{content}\r\n").ConfigureAwait(false);
                         if (transferError != TransferError.MissingChunkTerminator)
                         {
-                            await writer.WriteAsync("0\r\n\r\n").ConfigureAwait(false);
+                            await connection.WriteStringAsync("0\r\n\r\n").ConfigureAwait(false);
                         }
                     }
                     else
                     {
-                        await writer.WriteAsync($"{content}").ConfigureAwait(false);
+                        await connection.WriteStringAsync($"{content}").ConfigureAwait(false);
                     }
                 }));
         }
index 9a178da..16587b3 100644 (file)
@@ -52,7 +52,7 @@ namespace System.Net.Http.Functional.Tests
 
                             TextReader clientReader = new StreamReader(clientStream);
                             TextWriter clientWriter = new StreamWriter(clientStream) { AutoFlush = true };
-                            TextWriter serverWriter = connection.Writer;
+                            TextWriter serverWriter = new StreamWriter(connection.Stream, leaveOpen: true) { AutoFlush = true };
 
                             const string helloServer = "hello server";
                             const string helloClient = "hello client";
index 53e81d9..f3f264d 100644 (file)
@@ -391,10 +391,16 @@ namespace System.Net.Http.Functional.Tests
                 },
                 async server =>
                 {
-                    await server.HandleRequestAsync(headers: new[]
+                    // The client may detect the bad header and close the connection before we are done sending the response.
+                    // So, eat any IOException that occurs here.
+                    try
                     {
-                        new HttpHeaderData("", "foo")
-                    });
+                        await server.HandleRequestAsync(headers: new[]
+                        {
+                            new HttpHeaderData("", "foo")
+                        });
+                    }
+                    catch (IOException) { }
                 });
         }
 
index 8bd2083..6c17426 100644 (file)
@@ -128,7 +128,7 @@ namespace System.Net.Http.Functional.Tests
                         await connection.ReadRequestHeaderAsync().ConfigureAwait(false);
                         foreach (char c in response)
                         {
-                            await connection.Writer.WriteAsync(c);
+                            await connection.WriteStringAsync(c.ToString());
                         }
 
                         // Process the second request.
@@ -197,7 +197,7 @@ namespace System.Net.Http.Functional.Tests
                         await connection.ReadRequestHeaderAsync();
                         try
                         {
-                            await connection.Writer.WriteAsync(LoopbackServer.GetContentModeResponse(mode, content, connectionClose: false));
+                            await connection.WriteStringAsync(LoopbackServer.GetContentModeResponse(mode, content, connectionClose: false));
                         }
                         catch (Exception) { }     // Eat errors from client disconnect.
 
index e42252e..fb54f71 100644 (file)
@@ -88,7 +88,7 @@ namespace System.Net.Http.Functional.Tests
                     while (!string.IsNullOrEmpty(await connection.ReadLineAsync().ConfigureAwait(false)));
                     Assert.Equal(numBytes, await connection.ReadBlockAsync(postData, 0, numBytes));
 
-                    await connection.Writer.WriteAsync(responseText).ConfigureAwait(false);
+                    await connection.WriteStringAsync(responseText).ConfigureAwait(false);
                     connection.Socket.Shutdown(SocketShutdown.Send);
                 });
 
index 3fda220..2176fce 100644 (file)
@@ -1065,7 +1065,7 @@ namespace System.Net.Http.Functional.Tests
                             cts.Cancel();
                             for (int i = 0; i < 100; ++i)
                             {
-                                await connection.Writer.WriteLineAsync(content);
+                                await connection.WriteStringAsync(content);
                                 await Task.Delay(TimeSpan.FromSeconds(0.1));
                             }
                         }
@@ -1090,8 +1090,7 @@ namespace System.Net.Http.Functional.Tests
                 await connection.SendResponseAsync(headers: new[] { new HttpHeaderData("Content-Length", (Content.Length * 100).ToString()) });
                 for (int i = 0; i < 100; ++i)
                 {
-                    await connection.Writer.WriteLineAsync(Content);
-                    await connection.Writer.FlushAsync();
+                    await connection.WriteStringAsync(Content);
                     await Task.Delay(TimeSpan.FromSeconds(0.1));
                 }
             });
index 2369bc3..724f4a3 100644 (file)
@@ -408,7 +408,7 @@ namespace System.Net.Http.Functional.Tests
                         await connection.ReadRequestHeaderAsync();
                         try
                         {
-                            await connection.Writer.WriteAsync(LoopbackServer.GetContentModeResponse(mode, content, connectionClose: false));
+                            await connection.WriteStringAsync(LoopbackServer.GetContentModeResponse(mode, content, connectionClose: false));
                         }
                         catch (Exception) { }     // Eat errors from client disconnect.
 
@@ -460,7 +460,7 @@ namespace System.Net.Http.Functional.Tests
                         try
                         {
                             // Write out only part of the response
-                            await connection.Writer.WriteAsync(response.Substring(0, response.Length / 2));
+                            await connection.WriteStringAsync(response.Substring(0, response.Length / 2));
                         }
                         catch (Exception) { }     // Eat errors from client disconnect.
 
@@ -1324,7 +1324,7 @@ namespace System.Net.Http.Functional.Tests
 
                     Task serverTask1 = server.AcceptConnectionAsync(async connection =>
                     {
-                        await connection.Writer.WriteAsync(LoopbackServer.GetHttpResponse(connectionClose: false) + "here is a bunch of garbage");
+                        await connection.WriteStringAsync(LoopbackServer.GetHttpResponse(connectionClose: false) + "here is a bunch of garbage");
                         await releaseServer.Task; // keep connection alive on the server side
                     });
                     await client.GetStringAsync(uri);
index 12c364b..82ae0bf 100644 (file)
@@ -42,7 +42,7 @@ namespace System.Net.WebSockets.Client.Tests
             if (serverResponse != null)
             {
                 // We received a valid WebSocket opening handshake. Send the appropriate response.
-                await connection.Writer.WriteAsync(serverResponse).ConfigureAwait(false);
+                await connection.WriteStringAsync(serverResponse).ConfigureAwait(false);
                 return results;
             }