Skip to content

Commit

Permalink
Get rid of StreamWriter usage in HTTP loopback server and fix HTTP/1.…
Browse files Browse the repository at this point in the history
…1 loopback implementation of SendResponseBodyAsync (#47364)

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>
  • Loading branch information
geoffkizer and Geoffrey Kizer authored Jan 26, 2021
1 parent fdc8148 commit 59417ef
Show file tree
Hide file tree
Showing 15 changed files with 54 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ await LoopbackServer.CreateClientAndServerAsync(uri =>
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)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,10 @@ await LoopbackServer.CreateServerAsync(async (server, url) =>
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
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);
Expand Down Expand Up @@ -163,7 +163,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
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);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ await server.AcceptConnectionAsync(async connection =>
{
while (!cts.IsCancellationRequested)
{
await connection.Writer.WriteAsync(new string('s', 16000));
await connection.WriteStringAsync(new string('s', 16000));
await Task.Delay(1);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -969,12 +969,11 @@ await LoopbackServer.CreateServerAsync(async (server, url) =>
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);
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -442,18 +442,18 @@ await server.AcceptConnectionAsync(async connection =>
{
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);
});
});
}
Expand Down
22 changes: 14 additions & 8 deletions src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,6 @@ public sealed class Connection : GenericLoopbackConnection
private const int BufferSize = 4000;
private Socket _socket;
private Stream _stream;
private StreamWriter _writer;
private byte[] _readBuffer;
private int _readStart;
private int _readEnd;
Expand All @@ -409,16 +408,13 @@ public Connection(Socket socket, Stream stream)
_socket = socket;
_stream = stream;

_writer = new StreamWriter(stream, Encoding.ASCII) { AutoFlush = true };

_readBuffer = new byte[BufferSize];
_readStart = 0;
_readEnd = 0;
}

public Socket Socket => _socket;
public Stream Stream => _stream;
public StreamWriter Writer => _writer;

public static async Task<Connection> CreateAsync(Socket socket, Stream stream, Options httpOptions)
{
Expand Down Expand Up @@ -620,7 +616,6 @@ public override void Dispose()
}
catch (Exception) { }

_writer.Dispose();
_stream.Dispose();
_socket?.Dispose();
}
Expand Down Expand Up @@ -673,9 +668,20 @@ private async Task<List<byte[]>> ReadRequestHeaderBytesAsync()
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)
Expand All @@ -686,7 +692,7 @@ public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.O
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;
}

Expand Down Expand Up @@ -892,7 +898,7 @@ public override async Task SendResponseHeadersAsync(HttpStatusCode statusCode =

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 = "")
Expand Down
19 changes: 9 additions & 10 deletions src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -321,32 +321,31 @@ public static Task StartTransferTypeAndErrorServer(
}

// 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)
{
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);
}
}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ await server.AcceptConnectionAsync(async connection =>

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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,16 @@ await LoopbackServerFactory.CreateClientAndServerAsync(
},
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) { }
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ await server.AcceptConnectionAsync(async connection =>
await connection.ReadRequestHeaderAsync().ConfigureAwait(false);
foreach (char c in response)
{
await connection.Writer.WriteAsync(c);
await connection.WriteStringAsync(c.ToString());
}

// Process the second request.
Expand Down Expand Up @@ -197,7 +197,7 @@ await server.AcceptConnectionAsync(async connection =>
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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ await server.AcceptConnectionAsync(async connection =>
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);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,7 @@ await server.AcceptConnectionAsync(async connection =>
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));
}
}
Expand All @@ -1090,8 +1090,7 @@ public void Send_TimeoutResponseContent_Throws()
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));
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ await server.AcceptConnectionAsync(async connection =>
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.

Expand Down Expand Up @@ -460,7 +460,7 @@ await server.AcceptConnectionAsync(async connection =>
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.

Expand Down Expand Up @@ -1324,7 +1324,7 @@ await LoopbackServer.CreateServerAsync(async (server, uri) =>

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static async Task<Dictionary<string, string>> WebSocketHandshakeAsync(Loo
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;
}

Expand Down

0 comments on commit 59417ef

Please sign in to comment.