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>
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)),
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);
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);
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);
});
});
{
while (!cts.IsCancellationRequested)
{
- await connection.Writer.WriteAsync(new string('s', 16000));
+ await connection.WriteStringAsync(new string('s', 16000));
await Task.Delay(1);
}
}
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);
}
}
{
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);
});
});
}
private const int BufferSize = 4000;
private Socket _socket;
private Stream _stream;
- private StreamWriter _writer;
private byte[] _readBuffer;
private int _readStart;
private int _readEnd;
_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)
{
}
catch (Exception) { }
- _writer.Dispose();
_stream.Dispose();
_socket?.Dispose();
}
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)
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;
}
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 = "")
}
// 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);
}
}));
}
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";
},
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) { }
});
}
await connection.ReadRequestHeaderAsync().ConfigureAwait(false);
foreach (char c in response)
{
- await connection.Writer.WriteAsync(c);
+ await connection.WriteStringAsync(c.ToString());
}
// Process the second request.
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.
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);
});
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));
}
}
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));
}
});
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.
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.
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);
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;
}