public byte[] Data;
public PingFrame(byte[] data, FrameFlags flags, int streamId) :
- base(Frame.FrameHeaderLength + 8, FrameType.Ping, flags, streamId)
+ base(8, FrameType.Ping, flags, streamId)
{
Data = data;
}
public static PingFrame ReadFrom(Frame header, ReadOnlySpan<byte> buffer)
{
- byte[] data = buffer.Slice(Frame.FrameHeaderLength).ToArray();
+ byte[] data = buffer.ToArray();
return new PingFrame(data, header.Flags, header.StreamId);
}
public override void WriteTo(Span<byte> buffer)
{
base.WriteTo(buffer);
+ buffer = buffer.Slice(Frame.FrameHeaderLength, 8);
- Data.CopyTo(buffer.Slice(Frame.FrameHeaderLength));
+ Data.CopyTo(buffer);
}
public override string ToString()
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Authentication;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Xunit;
+
+namespace System.Net.Test.Common
+{
+ public class Http2LoopbackConnection
+ {
+ private Socket _connectionSocket;
+ private Stream _connectionStream;
+ private bool _ignoreSettingsAck;
+ private bool _ignoreWindowUpdates;
+ public static TimeSpan Timeout => Http2LoopbackServer.Timeout;
+
+ private readonly byte[] _prefix;
+ public string PrefixString => Encoding.UTF8.GetString(_prefix, 0, _prefix.Length);
+ public bool IsInvalid => _connectionSocket == null;
+
+ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)
+ {
+ _connectionSocket = socket;
+ _connectionStream = new NetworkStream(_connectionSocket, true);
+
+ if (httpOptions.UseSsl)
+ {
+ var sslStream = new SslStream(_connectionStream, false, delegate { return true; });
+
+ using (var cert = Configuration.Certificates.GetServerCertificate())
+ {
+ SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();
+
+ options.EnabledSslProtocols = httpOptions.SslProtocols;
+
+ var protocols = new List<SslApplicationProtocol>();
+ protocols.Add(SslApplicationProtocol.Http2);
+ protocols.Add(SslApplicationProtocol.Http11);
+ options.ApplicationProtocols = protocols;
+
+ options.ServerCertificate = cert;
+
+ options.ClientCertificateRequired = false;
+
+ sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).Wait();
+ }
+
+ _connectionStream = sslStream;
+ }
+
+ _prefix = new byte[24];
+ if (!FillBufferAsync(_prefix).Result)
+ {
+ throw new Exception("Connection stream closed while attempting to read connection preface.");
+ }
+ }
+
+ public async Task SendConnectionPrefaceAsync()
+ {
+ // Send the initial server settings frame.
+ Frame emptySettings = new Frame(0, FrameType.Settings, FrameFlags.None, 0);
+ await WriteFrameAsync(emptySettings).ConfigureAwait(false);
+
+ // Receive and ACK the client settings frame.
+ Frame clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ clientSettings.Flags = clientSettings.Flags | FrameFlags.Ack;
+ await WriteFrameAsync(clientSettings).ConfigureAwait(false);
+
+ // Receive the client ACK of the server settings frame.
+ clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ }
+
+ public async Task WriteFrameAsync(Frame frame)
+ {
+ byte[] writeBuffer = new byte[Frame.FrameHeaderLength + frame.Length];
+ frame.WriteTo(writeBuffer);
+ await _connectionStream.WriteAsync(writeBuffer, 0, writeBuffer.Length).ConfigureAwait(false);
+ }
+
+ // Read until the buffer is full
+ // Return false on EOF, throw on partial read
+ private async Task<bool> FillBufferAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ int readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+ if (readBytes == 0)
+ {
+ return false;
+ }
+
+ buffer = buffer.Slice(readBytes);
+ while (buffer.Length > 0)
+ {
+ readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+ if (readBytes == 0)
+ {
+ throw new Exception("Connection closed when expecting more data.");
+ }
+
+ buffer = buffer.Slice(readBytes);
+ }
+
+ return true;
+ }
+
+ public async Task<Frame> ReadFrameAsync(TimeSpan timeout)
+ {
+ using CancellationTokenSource timeoutCts = new CancellationTokenSource(timeout);
+ return await ReadFrameAsync(timeoutCts.Token);
+ }
+
+ private async Task<Frame> ReadFrameAsync(CancellationToken cancellationToken)
+ {
+ // First read the frame headers, which should tell us how long the rest of the frame is.
+ byte[] headerBytes = new byte[Frame.FrameHeaderLength];
+
+ try
+ {
+ if (!await FillBufferAsync(headerBytes, cancellationToken).ConfigureAwait(false))
+ {
+ return null;
+ }
+ }
+ catch (IOException)
+ {
+ // eat errors when client aborts connection and return null.
+ return null;
+ }
+
+ Frame header = Frame.ReadFrom(headerBytes);
+
+ // Read the data segment of the frame, if it is present.
+ byte[] data = new byte[header.Length];
+ if (header.Length > 0 && !await FillBufferAsync(data, cancellationToken).ConfigureAwait(false))
+ {
+ throw new Exception("Connection stream closed while attempting to read frame body.");
+ }
+
+ if (_ignoreSettingsAck && header.Type == FrameType.Settings && header.Flags == FrameFlags.Ack)
+ {
+ _ignoreSettingsAck = false;
+ return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ if (_ignoreWindowUpdates && header.Type == FrameType.WindowUpdate)
+ {
+ return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ // Construct the correct frame type and return it.
+ switch (header.Type)
+ {
+ case FrameType.Settings:
+ return SettingsFrame.ReadFrom(header, data);
+ case FrameType.Data:
+ return DataFrame.ReadFrom(header, data);
+ case FrameType.Headers:
+ return HeadersFrame.ReadFrom(header, data);
+ case FrameType.Priority:
+ return PriorityFrame.ReadFrom(header, data);
+ case FrameType.RstStream:
+ return RstStreamFrame.ReadFrom(header, data);
+ case FrameType.Ping:
+ return PingFrame.ReadFrom(header, data);
+ case FrameType.GoAway:
+ return GoAwayFrame.ReadFrom(header, data);
+ default:
+ return header;
+ }
+ }
+
+ // Reset and return underlying networking objects.
+ public (Socket, Stream) ResetNetwork()
+ {
+ Socket oldSocket = _connectionSocket;
+ Stream oldStream = _connectionStream;
+ _connectionSocket = null;
+ _connectionStream = null;
+ _ignoreSettingsAck = false;
+
+ return (oldSocket, oldStream);
+ }
+
+ public void ExpectSettingsAck()
+ {
+ // The timing of when we receive the settings ack is not guaranteed.
+ // To simplify frame processing, just record that we are expecting one,
+ // and then filter it out in ReadFrameAsync above.
+
+ Assert.False(_ignoreSettingsAck);
+ _ignoreSettingsAck = true;
+ }
+
+ public void IgnoreWindowUpdates()
+ {
+ _ignoreWindowUpdates = true;
+ }
+
+ public void ShutdownSend()
+ {
+ _connectionSocket.Shutdown(SocketShutdown.Send);
+ }
+
+ // This will wait for the client to close the connection,
+ // and ignore any meaningless frames -- i.e. WINDOW_UPDATE or expected SETTINGS ACK --
+ // that we see while waiting for the client to close.
+ // Only call this after sending a GOAWAY.
+ public async Task WaitForConnectionShutdownAsync()
+ {
+ // Shutdown our send side, so the client knows there won't be any more frames coming.
+ ShutdownSend();
+
+ IgnoreWindowUpdates();
+ Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ if (frame != null)
+ {
+ throw new Exception($"Unexpected frame received while waiting for client shutdown: {frame}");
+ }
+
+ _connectionStream.Close();
+
+ _connectionSocket = null;
+ _connectionStream = null;
+
+ _ignoreSettingsAck = false;
+ _ignoreWindowUpdates = false;
+ }
+
+ public async Task<int> ReadRequestHeaderAsync()
+ {
+ // Receive HEADERS frame for request.
+ Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ if (frame == null)
+ {
+ throw new IOException("Failed to read Headers frame.");
+ }
+ Assert.Equal(FrameType.Headers, frame.Type);
+ Assert.Equal(FrameFlags.EndHeaders | FrameFlags.EndStream, frame.Flags);
+ return frame.StreamId;
+ }
+
+ private static (int bytesConsumed, int value) DecodeInteger(ReadOnlySpan<byte> headerBlock, byte prefixMask)
+ {
+ int value = headerBlock[0] & prefixMask;
+ if (value != prefixMask)
+ {
+ return (1, value);
+ }
+
+ byte b = headerBlock[1];
+ if ((b & 0b10000000) != 0)
+ {
+ throw new Exception("long integers currently not supported");
+ }
+ return (2, prefixMask + b);
+ }
+
+ private static int EncodeInteger(int value, byte prefix, byte prefixMask, Span<byte> headerBlock)
+ {
+ byte prefixLimit = (byte)(~prefixMask);
+
+ if (value < prefixLimit)
+ {
+ headerBlock[0] = (byte)(prefix | value);
+ return 1;
+ }
+
+ headerBlock[0] = (byte)(prefix | prefixLimit);
+ int bytesGenerated = 1;
+
+ value -= prefixLimit;
+
+ while (value >= 0x80)
+ {
+ headerBlock[bytesGenerated] = (byte)((value & 0x7F) | 0x80);
+ value = value >> 7;
+ bytesGenerated++;
+ }
+
+ headerBlock[bytesGenerated] = (byte)value;
+ bytesGenerated++;
+
+ return bytesGenerated;
+ }
+
+ private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan<byte> headerBlock)
+ {
+ (int bytesConsumed, int stringLength) = DecodeInteger(headerBlock, 0b01111111);
+ if ((headerBlock[0] & 0b10000000) != 0)
+ {
+ // Huffman encoded
+ byte[] buffer = new byte[stringLength * 2];
+ int bytesDecoded = HuffmanDecoder.Decode(headerBlock.Slice(bytesConsumed, stringLength), buffer);
+ string value = Encoding.ASCII.GetString(buffer, 0, bytesDecoded);
+ return (bytesConsumed + stringLength, value);
+ }
+ else
+ {
+ string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength));
+ return (bytesConsumed + stringLength, value);
+ }
+ }
+
+ private static int EncodeString(string value, Span<byte> headerBlock)
+ {
+ int bytesGenerated = EncodeInteger(value.Length, 0, 0x80, headerBlock);
+
+ bytesGenerated += Encoding.ASCII.GetBytes(value.AsSpan(), headerBlock.Slice(bytesGenerated));
+
+ return bytesGenerated;
+ }
+
+ private static readonly HttpHeaderData[] s_staticTable = new HttpHeaderData[]
+ {
+ new HttpHeaderData(":authority", ""),
+ new HttpHeaderData(":method", "GET"),
+ new HttpHeaderData(":method", "POST"),
+ new HttpHeaderData(":path", "/"),
+ new HttpHeaderData(":path", "/index.html"),
+ new HttpHeaderData(":scheme", "http"),
+ new HttpHeaderData(":scheme", "https"),
+ new HttpHeaderData(":status", "200"),
+ new HttpHeaderData(":status", "204"),
+ new HttpHeaderData(":status", "206"),
+ new HttpHeaderData(":status", "304"),
+ new HttpHeaderData(":status", "400"),
+ new HttpHeaderData(":status", "404"),
+ new HttpHeaderData(":status", "500"),
+ new HttpHeaderData("accept-charset", ""),
+ new HttpHeaderData("accept-encoding", "gzip, deflate"),
+ new HttpHeaderData("accept-language", ""),
+ new HttpHeaderData("accept-ranges", ""),
+ new HttpHeaderData("accept", ""),
+ new HttpHeaderData("access-control-allow-origin", ""),
+ new HttpHeaderData("age", ""),
+ new HttpHeaderData("allow", ""),
+ new HttpHeaderData("authorization", ""),
+ new HttpHeaderData("cache-control", ""),
+ new HttpHeaderData("content-disposition", ""),
+ new HttpHeaderData("content-encoding", ""),
+ new HttpHeaderData("content-language", ""),
+ new HttpHeaderData("content-length", ""),
+ new HttpHeaderData("content-location", ""),
+ new HttpHeaderData("content-range", ""),
+ new HttpHeaderData("content-type", ""),
+ new HttpHeaderData("cookie", ""),
+ new HttpHeaderData("date", ""),
+ new HttpHeaderData("etag", ""),
+ new HttpHeaderData("expect", ""),
+ new HttpHeaderData("expires", ""),
+ new HttpHeaderData("from", ""),
+ new HttpHeaderData("host", ""),
+ new HttpHeaderData("if-match", ""),
+ new HttpHeaderData("if-modified-since", ""),
+ new HttpHeaderData("if-none-match", ""),
+ new HttpHeaderData("if-range", ""),
+ new HttpHeaderData("if-unmodified-since", ""),
+ new HttpHeaderData("last-modified", ""),
+ new HttpHeaderData("link", ""),
+ new HttpHeaderData("location", ""),
+ new HttpHeaderData("max-forwards", ""),
+ new HttpHeaderData("proxy-authenticate", ""),
+ new HttpHeaderData("proxy-authorization", ""),
+ new HttpHeaderData("range", ""),
+ new HttpHeaderData("referer", ""),
+ new HttpHeaderData("refresh", ""),
+ new HttpHeaderData("retry-after", ""),
+ new HttpHeaderData("server", ""),
+ new HttpHeaderData("set-cookie", ""),
+ new HttpHeaderData("strict-transport-security", ""),
+ new HttpHeaderData("transfer-encoding", ""),
+ new HttpHeaderData("user-agent", ""),
+ new HttpHeaderData("vary", ""),
+ new HttpHeaderData("via", ""),
+ new HttpHeaderData("www-authenticate", "")
+ };
+
+ private static HttpHeaderData GetHeaderForIndex(int index)
+ {
+ return s_staticTable[index - 1];
+ }
+
+ private static (int bytesConsumed, HttpHeaderData headerData) DecodeLiteralHeader(ReadOnlySpan<byte> headerBlock, byte prefixMask)
+ {
+ int i = 0;
+
+ (int bytesConsumed, int index) = DecodeInteger(headerBlock, prefixMask);
+ i += bytesConsumed;
+
+ string name;
+ if (index == 0)
+ {
+ (bytesConsumed, name) = DecodeString(headerBlock.Slice(i));
+ i += bytesConsumed;
+ }
+ else
+ {
+ name = GetHeaderForIndex(index).Name;
+ }
+
+ string value;
+ (bytesConsumed, value) = DecodeString(headerBlock.Slice(i));
+ i += bytesConsumed;
+
+ return (i, new HttpHeaderData(name, value));
+ }
+
+ private static (int bytesConsumed, HttpHeaderData headerData) DecodeHeader(ReadOnlySpan<byte> headerBlock)
+ {
+ int i = 0;
+
+ byte b = headerBlock[0];
+ if ((b & 0b10000000) != 0)
+ {
+ // Indexed header
+ (int bytesConsumed, int index) = DecodeInteger(headerBlock, 0b01111111);
+ i += bytesConsumed;
+
+ return (i, GetHeaderForIndex(index));
+ }
+ else if ((b & 0b11000000) == 0b01000000)
+ {
+ // Literal with indexing
+ return DecodeLiteralHeader(headerBlock, 0b00111111);
+ }
+ else if ((b & 0b11100000) == 0b00100000)
+ {
+ // Table size update
+ throw new Exception("table size update not supported");
+ }
+ else
+ {
+ // Literal, never indexed
+ return DecodeLiteralHeader(headerBlock, 0b00001111);
+ }
+ }
+
+ private static int EncodeHeader(HttpHeaderData headerData, Span<byte> headerBlock)
+ {
+ // Always encode as literal, no indexing
+ int bytesGenerated = EncodeInteger(0, 0, 0b11110000, headerBlock);
+ bytesGenerated += EncodeString(headerData.Name, headerBlock.Slice(bytesGenerated));
+ bytesGenerated += EncodeString(headerData.Value, headerBlock.Slice(bytesGenerated));
+ return bytesGenerated;
+ }
+
+ public async Task<byte[]> ReadBodyAsync()
+ {
+ byte[] body = null;
+ Frame frame;
+
+ do
+ {
+ frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ if (frame == null || frame.Type == FrameType.RstStream)
+ {
+ throw new IOException( frame == null ? "End of stream" : "Got RST");
+ }
+
+ Assert.Equal(FrameType.Data, frame.Type);
+
+ if (frame.Length > 1)
+ {
+ DataFrame dataFrame = (DataFrame)frame;
+
+ if (body == null)
+ {
+ body = dataFrame.Data.ToArray();
+ }
+ else
+ {
+ byte[] newBuffer = new byte[body.Length + dataFrame.Data.Length];
+
+ body.CopyTo(newBuffer, 0);
+ dataFrame.Data.Span.CopyTo(newBuffer.AsSpan().Slice(body.Length));
+ body= newBuffer;
+ }
+ }
+ }
+ while ((frame.Flags & FrameFlags.EndStream) == 0);
+
+ return body;
+ }
+
+ public async Task<(int streamId, HttpRequestData requestData)> ReadAndParseRequestHeaderAsync(bool readBody = true)
+ {
+ HttpRequestData requestData = new HttpRequestData();
+
+ // Receive HEADERS frame for request.
+ Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ if (frame == null)
+ {
+ throw new IOException("Failed to read Headers frame.");
+ }
+ Assert.Equal(FrameType.Headers, frame.Type);
+ HeadersFrame headersFrame = (HeadersFrame) frame;
+
+ // TODO CONTINUATION support
+ Assert.Equal(FrameFlags.EndHeaders, FrameFlags.EndHeaders & headersFrame.Flags);
+
+ int streamId = headersFrame.StreamId;
+
+ Memory<byte> data = headersFrame.Data;
+ int i = 0;
+ while (i < data.Length)
+ {
+ (int bytesConsumed, HttpHeaderData headerData) = DecodeHeader(data.Span.Slice(i));
+
+ byte[] headerRaw = data.Span.Slice(i, bytesConsumed).ToArray();
+ headerData = new HttpHeaderData(headerData.Name, headerData.Value, headerRaw);
+
+ requestData.Headers.Add(headerData);
+ i += bytesConsumed;
+ }
+
+ // Extract method and path
+ requestData.Method = requestData.GetSingleHeaderValue(":method");
+ requestData.Path = requestData.GetSingleHeaderValue(":path");
+
+ if (readBody && (frame.Flags & FrameFlags.EndStream) == 0)
+ {
+ // Read body until end of stream if needed.
+ requestData.Body = await ReadBodyAsync();
+ }
+
+ return (streamId, requestData);
+ }
+
+ public async Task SendGoAway(int lastStreamId)
+ {
+ GoAwayFrame frame = new GoAwayFrame(lastStreamId, 0, new byte[] { }, 0);
+ await WriteFrameAsync(frame).ConfigureAwait(false);
+ }
+
+ public async Task PingPong()
+ {
+ PingFrame ping = new PingFrame(new byte[8] { 1, 2, 3, 4, 50, 60, 70, 80 }, FrameFlags.None, 0);
+ await WriteFrameAsync(ping).ConfigureAwait(false);
+ await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ }
+
+ public async Task SendDefaultResponseHeadersAsync(int streamId)
+ {
+ byte[] headers = new byte[] { 0x88 }; // Encoding for ":status: 200"
+
+ HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders, 0, 0, 0, streamId);
+ await WriteFrameAsync(headersFrame).ConfigureAwait(false);
+ }
+
+ public async Task SendDefaultResponseAsync(int streamId)
+ {
+ byte[] headers = new byte[] { 0x88 }; // Encoding for ":status: 200"
+
+ HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders | FrameFlags.EndStream, 0, 0, 0, streamId);
+ await WriteFrameAsync(headersFrame).ConfigureAwait(false);
+ }
+
+ public async Task SendResponseHeadersAsync(int streamId, bool endStream = true, HttpStatusCode statusCode = HttpStatusCode.OK, bool isTrailingHeader = false, IList<HttpHeaderData> headers = null)
+ {
+ // For now, only support headers that fit in a single frame
+ byte[] headerBlock = new byte[Frame.MaxFrameLength];
+ int bytesGenerated = 0;
+
+ if (!isTrailingHeader)
+ {
+ string statusCodeString = ((int)statusCode).ToString();
+ bytesGenerated += EncodeHeader(new HttpHeaderData(":status", statusCodeString), headerBlock.AsSpan());
+ }
+
+ if (headers != null)
+ {
+ foreach (HttpHeaderData headerData in headers)
+ {
+ bytesGenerated += EncodeHeader(headerData, headerBlock.AsSpan().Slice(bytesGenerated));
+ }
+ }
+
+ FrameFlags flags = FrameFlags.EndHeaders;
+ if (endStream)
+ {
+ flags |= FrameFlags.EndStream;
+ }
+
+ HeadersFrame headersFrame = new HeadersFrame(headerBlock.AsMemory().Slice(0, bytesGenerated), flags, 0, 0, 0, streamId);
+ await WriteFrameAsync(headersFrame).ConfigureAwait(false);
+ }
+
+ public async Task SendResponseDataAsync(int streamId, ReadOnlyMemory<byte> responseData, bool endStream)
+ {
+ DataFrame dataFrame = new DataFrame(responseData, endStream ? FrameFlags.EndStream : FrameFlags.None, 0, streamId);
+ await WriteFrameAsync(dataFrame).ConfigureAwait(false);
+ }
+
+ public async Task SendResponseBodyAsync(int streamId, ReadOnlyMemory<byte> responseBody)
+ {
+ // Only support response body if it fits in a single frame, for now
+ // In the future we should separate the body into chunks as needed,
+ // and if it's larger than the default window size, we will need to process window updates as well.
+ if (responseBody.Length > Frame.MaxFrameLength)
+ {
+ throw new Exception("Response body too long");
+ }
+
+ await SendResponseDataAsync(streamId, responseBody, true).ConfigureAwait(false);
+ }
+ }
+}
public class Http2LoopbackServer : GenericLoopbackServer, IDisposable
{
private Socket _listenSocket;
- private Socket _connectionSocket;
- private Stream _connectionStream;
private Http2Options _options;
private Uri _uri;
- private bool _ignoreSettingsAck;
- private bool _ignoreWindowUpdates;
- public TimeSpan Timeout = TimeSpan.FromSeconds(30);
+ private List<Http2LoopbackConnection> _connections = new List<Http2LoopbackConnection>();
+
+ public bool AllowMultipleConnections { get; set; }
+
+ private Http2LoopbackConnection Connection
+ {
+ get
+ {
+ RemoveInvalidConnections();
+ return _connections[0];
+ }
+ }
+
+ public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
public Uri Address
{
_listenSocket.Listen(_options.ListenBacklog);
}
- public async Task SendConnectionPrefaceAsync()
- {
- // Send the initial server settings frame.
- Frame emptySettings = new Frame(0, FrameType.Settings, FrameFlags.None, 0);
- await WriteFrameAsync(emptySettings).ConfigureAwait(false);
-
- // Receive and ACK the client settings frame.
- Frame clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
- clientSettings.Flags = clientSettings.Flags | FrameFlags.Ack;
- await WriteFrameAsync(clientSettings).ConfigureAwait(false);
-
- // Receive the client ACK of the server settings frame.
- clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
- }
-
- public async Task WriteFrameAsync(Frame frame)
- {
- byte[] writeBuffer = new byte[Frame.FrameHeaderLength + frame.Length];
- frame.WriteTo(writeBuffer);
- await _connectionStream.WriteAsync(writeBuffer, 0, writeBuffer.Length).ConfigureAwait(false);
- }
-
- // Read until the buffer is full
- // Return false on EOF, throw on partial read
- private async Task<bool> FillBufferAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
- {
- int readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
- if (readBytes == 0)
- {
- return false;
- }
-
- buffer = buffer.Slice(readBytes);
- while (buffer.Length > 0)
- {
- readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
- if (readBytes == 0)
- {
- throw new Exception("Connection closed when expecting more data.");
- }
-
- buffer = buffer.Slice(readBytes);
- }
-
- return true;
- }
-
- public async Task<Frame> ReadFrameAsync(TimeSpan timeout)
- {
- using CancellationTokenSource timeoutCts = new CancellationTokenSource(timeout);
- return await ReadFrameAsync(timeoutCts.Token);
- }
-
- private async Task<Frame> ReadFrameAsync(CancellationToken cancellationToken)
- {
- // Prep the timeout cancellation token.
-
- // First read the frame headers, which should tell us how long the rest of the frame is.
- byte[] headerBytes = new byte[Frame.FrameHeaderLength];
-
- try
- {
- if (!await FillBufferAsync(headerBytes, cancellationToken).ConfigureAwait(false))
- {
- return null;
- }
- }
- catch (IOException)
- {
- // eat errors when client aborts connection and return null.
- return null;
- }
-
- Frame header = Frame.ReadFrom(headerBytes);
-
- // Read the data segment of the frame, if it is present.
- byte[] data = new byte[header.Length];
- if (header.Length > 0 && !await FillBufferAsync(data, cancellationToken).ConfigureAwait(false))
- {
- throw new Exception("Connection stream closed while attempting to read frame body.");
- }
-
- if (_ignoreSettingsAck && header.Type == FrameType.Settings && header.Flags == FrameFlags.Ack)
- {
- _ignoreSettingsAck = false;
- return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
- }
-
- if (_ignoreWindowUpdates && header.Type == FrameType.WindowUpdate)
- {
- return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
- }
-
- // Construct the correct frame type and return it.
- switch (header.Type)
- {
- case FrameType.Settings:
- return SettingsFrame.ReadFrom(header, data);
- case FrameType.Data:
- return DataFrame.ReadFrom(header, data);
- case FrameType.Headers:
- return HeadersFrame.ReadFrom(header, data);
- case FrameType.Priority:
- return PriorityFrame.ReadFrom(header, data);
- case FrameType.RstStream:
- return RstStreamFrame.ReadFrom(header, data);
- case FrameType.Ping:
- return PingFrame.ReadFrom(header, data);
- case FrameType.GoAway:
- return GoAwayFrame.ReadFrom(header, data);
- default:
- return header;
- }
- }
-
- // Reset and return underlying networking objects.
- public (Socket, Stream) ResetNetwork()
+ private void RemoveInvalidConnections()
{
- Socket oldSocket = _connectionSocket;
- Stream oldStream = _connectionStream;
- _connectionSocket = null;
- _connectionStream = null;
- _ignoreSettingsAck = false;
-
- return (oldSocket, oldStream);
+ _connections.RemoveAll((c) => c.IsInvalid);
}
- // Returns the first 24 bytes read, which should be the connection preface.
- public async Task<string> AcceptConnectionAsync()
+ public async Task<Http2LoopbackConnection> AcceptConnectionAsync()
{
- if (_connectionSocket != null)
- {
- throw new InvalidOperationException("Connection already established");
- }
-
- _connectionSocket = await _listenSocket.AcceptAsync().ConfigureAwait(false);
- _connectionStream = new NetworkStream(_connectionSocket, true);
+ RemoveInvalidConnections();
- if (_options.UseSsl)
+ if (!AllowMultipleConnections && _connections.Count != 0)
{
- var sslStream = new SslStream(_connectionStream, false, delegate { return true; });
- using (var cert = Configuration.Certificates.GetServerCertificate())
- {
- SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();
-
- options.EnabledSslProtocols = _options.SslProtocols;
-
- var protocols = new List<SslApplicationProtocol>();
- protocols.Add(SslApplicationProtocol.Http2);
- protocols.Add(SslApplicationProtocol.Http11);
- options.ApplicationProtocols = protocols;
-
- options.ServerCertificate = cert;
-
- options.ClientCertificateRequired = false;
-
- await sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).ConfigureAwait(false);
- }
- _connectionStream = sslStream;
- }
-
- byte[] prefix = new byte[24];
- if (!await FillBufferAsync(prefix).ConfigureAwait(false))
- {
- throw new Exception("Connection stream closed while attempting to read connection preface.");
+ throw new InvalidOperationException("Connection already established. Set `AllowMultipleConnections = true` to bypass.");
}
- return System.Text.Encoding.UTF8.GetString(prefix, 0, prefix.Length);
- }
+ Socket connectionSocket = await _listenSocket.AcceptAsync().ConfigureAwait(false);
- public void ExpectSettingsAck()
- {
- // The timing of when we receive the settings ack is not guaranteed.
- // To simplify frame processing, just record that we are expecting one,
- // and then filter it out in ReadFrameAsync above.
+ Http2LoopbackConnection connection = new Http2LoopbackConnection(connectionSocket, _options);
+ _connections.Add(connection);
- Assert.False(_ignoreSettingsAck);
- _ignoreSettingsAck = true;
+ return connection;
}
- public void IgnoreWindowUpdates()
+ public async Task<Http2LoopbackConnection> EstablishConnectionAsync(params SettingsEntry[] settingsEntries)
{
- _ignoreWindowUpdates = true;
+ (Http2LoopbackConnection connection, _) = await EstablishConnectionGetSettingsAsync();
+ return connection;
}
- // Accept connection and handle connection setup
- public async Task<SettingsFrame> EstablishConnectionAsync(params SettingsEntry[] settingsEntries)
+ public async Task<(Http2LoopbackConnection, SettingsFrame)> EstablishConnectionGetSettingsAsync(params SettingsEntry[] settingsEntries)
{
- await AcceptConnectionAsync().ConfigureAwait(false);
+ Http2LoopbackConnection connection = await AcceptConnectionAsync().ConfigureAwait(false);
// Receive the initial client settings frame.
- Frame receivedFrame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ Frame receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
Assert.Equal(FrameType.Settings, receivedFrame.Type);
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
Assert.Equal(0, receivedFrame.StreamId);
var clientSettingsFrame = (SettingsFrame)receivedFrame;
// Receive the initial client window update frame.
- receivedFrame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+ receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
Assert.Equal(FrameType.WindowUpdate, receivedFrame.Type);
Assert.Equal(FrameFlags.None, receivedFrame.Flags);
Assert.Equal(0, receivedFrame.StreamId);
// Send the initial server settings frame.
SettingsFrame settingsFrame = new SettingsFrame(settingsEntries);
- await WriteFrameAsync(settingsFrame).ConfigureAwait(false);
+ await connection.WriteFrameAsync(settingsFrame).ConfigureAwait(false);
// Send the client settings frame ACK.
Frame settingsAck = new Frame(0, FrameType.Settings, FrameFlags.Ack, 0);
- await WriteFrameAsync(settingsAck).ConfigureAwait(false);
+ await connection.WriteFrameAsync(settingsAck).ConfigureAwait(false);
// The client will send us a SETTINGS ACK eventually, but not necessarily right away.
- ExpectSettingsAck();
-
- return clientSettingsFrame;
- }
-
- public void ShutdownSend()
- {
- _connectionSocket.Shutdown(SocketShutdown.Send);
- }
-
- // This will wait for the client to close the connection,
- // and ignore any meaningless frames -- i.e. WINDOW_UPDATE or expected SETTINGS ACK --
- // that we see while waiting for the client to close.
- // Only call this after sending a GOAWAY.
- public async Task WaitForConnectionShutdownAsync()
- {
- // Shutdown our send side, so the client knows there won't be any more frames coming.
- ShutdownSend();
-
- IgnoreWindowUpdates();
- Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
- if (frame != null)
- {
- throw new Exception($"Unexpected frame received while waiting for client shutdown: {frame}");
- }
-
- _connectionStream.Close();
-
- _connectionSocket = null;
- _connectionStream = null;
-
- _ignoreSettingsAck = false;
- _ignoreWindowUpdates = false;
- }
-
- public async Task<int> ReadRequestHeaderAsync()
- {
- // Receive HEADERS frame for request.
- Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
- if (frame == null)
- {
- throw new IOException("Failed to read Headers frame.");
- }
- Assert.Equal(FrameType.Headers, frame.Type);
- Assert.Equal(FrameFlags.EndHeaders | FrameFlags.EndStream, frame.Flags);
- return frame.StreamId;
- }
-
- private static (int bytesConsumed, int value) DecodeInteger(ReadOnlySpan<byte> headerBlock, byte prefixMask)
- {
- int value = headerBlock[0] & prefixMask;
- if (value != prefixMask)
- {
- return (1, value);
- }
-
- byte b = headerBlock[1];
- if ((b & 0b10000000) != 0)
- {
- throw new Exception("long integers currently not supported");
- }
- return (2, prefixMask + b);
- }
-
- private static int EncodeInteger(int value, byte prefix, byte prefixMask, Span<byte> headerBlock)
- {
- byte prefixLimit = (byte)(~prefixMask);
-
- if (value < prefixLimit)
- {
- headerBlock[0] = (byte)(prefix | value);
- return 1;
- }
-
- headerBlock[0] = (byte)(prefix | prefixLimit);
- int bytesGenerated = 1;
-
- value -= prefixLimit;
-
- while (value >= 0x80)
- {
- headerBlock[bytesGenerated] = (byte)((value & 0x7F) | 0x80);
- value = value >> 7;
- bytesGenerated++;
- }
-
- headerBlock[bytesGenerated] = (byte)value;
- bytesGenerated++;
-
- return bytesGenerated;
- }
-
- private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan<byte> headerBlock)
- {
- (int bytesConsumed, int stringLength) = DecodeInteger(headerBlock, 0b01111111);
- if ((headerBlock[0] & 0b10000000) != 0)
- {
- // Huffman encoded
- byte[] buffer = new byte[stringLength * 2];
- int bytesDecoded = HuffmanDecoder.Decode(headerBlock.Slice(bytesConsumed, stringLength), buffer);
- string value = Encoding.ASCII.GetString(buffer, 0, bytesDecoded);
- return (bytesConsumed + stringLength, value);
- }
- else
- {
- string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength));
- return (bytesConsumed + stringLength, value);
- }
- }
-
- private static int EncodeString(string value, Span<byte> headerBlock)
- {
- int bytesGenerated = EncodeInteger(value.Length, 0, 0x80, headerBlock);
-
- bytesGenerated += Encoding.ASCII.GetBytes(value.AsSpan(), headerBlock.Slice(bytesGenerated));
-
- return bytesGenerated;
- }
-
- private static readonly HttpHeaderData[] s_staticTable = new HttpHeaderData[]
- {
- new HttpHeaderData(":authority", ""),
- new HttpHeaderData(":method", "GET"),
- new HttpHeaderData(":method", "POST"),
- new HttpHeaderData(":path", "/"),
- new HttpHeaderData(":path", "/index.html"),
- new HttpHeaderData(":scheme", "http"),
- new HttpHeaderData(":scheme", "https"),
- new HttpHeaderData(":status", "200"),
- new HttpHeaderData(":status", "204"),
- new HttpHeaderData(":status", "206"),
- new HttpHeaderData(":status", "304"),
- new HttpHeaderData(":status", "400"),
- new HttpHeaderData(":status", "404"),
- new HttpHeaderData(":status", "500"),
- new HttpHeaderData("accept-charset", ""),
- new HttpHeaderData("accept-encoding", "gzip, deflate"),
- new HttpHeaderData("accept-language", ""),
- new HttpHeaderData("accept-ranges", ""),
- new HttpHeaderData("accept", ""),
- new HttpHeaderData("access-control-allow-origin", ""),
- new HttpHeaderData("age", ""),
- new HttpHeaderData("allow", ""),
- new HttpHeaderData("authorization", ""),
- new HttpHeaderData("cache-control", ""),
- new HttpHeaderData("content-disposition", ""),
- new HttpHeaderData("content-encoding", ""),
- new HttpHeaderData("content-language", ""),
- new HttpHeaderData("content-length", ""),
- new HttpHeaderData("content-location", ""),
- new HttpHeaderData("content-range", ""),
- new HttpHeaderData("content-type", ""),
- new HttpHeaderData("cookie", ""),
- new HttpHeaderData("date", ""),
- new HttpHeaderData("etag", ""),
- new HttpHeaderData("expect", ""),
- new HttpHeaderData("expires", ""),
- new HttpHeaderData("from", ""),
- new HttpHeaderData("host", ""),
- new HttpHeaderData("if-match", ""),
- new HttpHeaderData("if-modified-since", ""),
- new HttpHeaderData("if-none-match", ""),
- new HttpHeaderData("if-range", ""),
- new HttpHeaderData("if-unmodified-since", ""),
- new HttpHeaderData("last-modified", ""),
- new HttpHeaderData("link", ""),
- new HttpHeaderData("location", ""),
- new HttpHeaderData("max-forwards", ""),
- new HttpHeaderData("proxy-authenticate", ""),
- new HttpHeaderData("proxy-authorization", ""),
- new HttpHeaderData("range", ""),
- new HttpHeaderData("referer", ""),
- new HttpHeaderData("refresh", ""),
- new HttpHeaderData("retry-after", ""),
- new HttpHeaderData("server", ""),
- new HttpHeaderData("set-cookie", ""),
- new HttpHeaderData("strict-transport-security", ""),
- new HttpHeaderData("transfer-encoding", ""),
- new HttpHeaderData("user-agent", ""),
- new HttpHeaderData("vary", ""),
- new HttpHeaderData("via", ""),
- new HttpHeaderData("www-authenticate", "")
- };
-
- private static HttpHeaderData GetHeaderForIndex(int index)
- {
- return s_staticTable[index - 1];
- }
-
- private static (int bytesConsumed, HttpHeaderData headerData) DecodeLiteralHeader(ReadOnlySpan<byte> headerBlock, byte prefixMask)
- {
- int i = 0;
-
- (int bytesConsumed, int index) = DecodeInteger(headerBlock, prefixMask);
- i += bytesConsumed;
-
- string name;
- if (index == 0)
- {
- (bytesConsumed, name) = DecodeString(headerBlock.Slice(i));
- i += bytesConsumed;
- }
- else
- {
- name = GetHeaderForIndex(index).Name;
- }
-
- string value;
- (bytesConsumed, value) = DecodeString(headerBlock.Slice(i));
- i += bytesConsumed;
-
- return (i, new HttpHeaderData(name, value));
- }
-
- private static (int bytesConsumed, HttpHeaderData headerData) DecodeHeader(ReadOnlySpan<byte> headerBlock)
- {
- int i = 0;
-
- byte b = headerBlock[0];
- if ((b & 0b10000000) != 0)
- {
- // Indexed header
- (int bytesConsumed, int index) = DecodeInteger(headerBlock, 0b01111111);
- i += bytesConsumed;
-
- return (i, GetHeaderForIndex(index));
- }
- else if ((b & 0b11000000) == 0b01000000)
- {
- // Literal with indexing
- return DecodeLiteralHeader(headerBlock, 0b00111111);
- }
- else if ((b & 0b11100000) == 0b00100000)
- {
- // Table size update
- throw new Exception("table size update not supported");
- }
- else
- {
- // Literal, never indexed
- return DecodeLiteralHeader(headerBlock, 0b00001111);
- }
- }
-
- private static int EncodeHeader(HttpHeaderData headerData, Span<byte> headerBlock)
- {
- // Always encode as literal, no indexing
- int bytesGenerated = EncodeInteger(0, 0, 0b11110000, headerBlock);
- bytesGenerated += EncodeString(headerData.Name, headerBlock.Slice(bytesGenerated));
- bytesGenerated += EncodeString(headerData.Value, headerBlock.Slice(bytesGenerated));
- return bytesGenerated;
- }
-
- public async Task<byte[]> ReadBodyAsync()
- {
- byte[] body = null;
- Frame frame;
-
- do
- {
- frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
- if (frame == null || frame.Type == FrameType.RstStream)
- {
- throw new IOException( frame == null ? "End of stream" : "Got RST");
- }
-
- Assert.Equal(FrameType.Data, frame.Type);
-
- if (frame.Length > 1)
- {
- DataFrame dataFrame = (DataFrame)frame;
-
- if (body == null)
- {
- body = dataFrame.Data.ToArray();
- }
- else
- {
- byte[] newBuffer = new byte[body.Length + dataFrame.Data.Length];
-
- body.CopyTo(newBuffer, 0);
- dataFrame.Data.Span.CopyTo(newBuffer.AsSpan().Slice(body.Length));
- body= newBuffer;
- }
- }
- }
- while ((frame.Flags & FrameFlags.EndStream) == 0);
-
- return body;
- }
-
- public async Task<(int streamId, HttpRequestData requestData)> ReadAndParseRequestHeaderAsync(bool readBody = true)
- {
- HttpRequestData requestData = new HttpRequestData();
-
- // Receive HEADERS frame for request.
- Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
- if (frame == null)
- {
- throw new IOException("Failed to read Headers frame.");
- }
- Assert.Equal(FrameType.Headers, frame.Type);
- HeadersFrame headersFrame = (HeadersFrame) frame;
-
- // TODO CONTINUATION support
- Assert.Equal(FrameFlags.EndHeaders, FrameFlags.EndHeaders & headersFrame.Flags);
-
- int streamId = headersFrame.StreamId;
-
- Memory<byte> data = headersFrame.Data;
- int i = 0;
- while (i < data.Length)
- {
- (int bytesConsumed, HttpHeaderData headerData) = DecodeHeader(data.Span.Slice(i));
-
- byte[] headerRaw = data.Span.Slice(i, bytesConsumed).ToArray();
- headerData = new HttpHeaderData(headerData.Name, headerData.Value, headerRaw);
-
- requestData.Headers.Add(headerData);
- i += bytesConsumed;
- }
-
- // Extract method and path
- requestData.Method = requestData.GetSingleHeaderValue(":method");
- requestData.Path = requestData.GetSingleHeaderValue(":path");
-
- if (readBody && (frame.Flags & FrameFlags.EndStream) == 0)
- {
- // Read body until end of stream if needed.
- requestData.Body = await ReadBodyAsync();
- }
-
- return (streamId, requestData);
- }
-
- public async Task SendGoAway(int lastStreamId)
- {
- GoAwayFrame frame = new GoAwayFrame(lastStreamId, 0, new byte[] { }, 0);
- await WriteFrameAsync(frame).ConfigureAwait(false);
- }
-
- public async Task SendDefaultResponseHeadersAsync(int streamId)
- {
- byte[] headers = new byte[] { 0x88 }; // Encoding for ":status: 200"
-
- HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders, 0, 0, 0, streamId);
- await WriteFrameAsync(headersFrame).ConfigureAwait(false);
- }
-
- public async Task SendDefaultResponseAsync(int streamId)
- {
- byte[] headers = new byte[] { 0x88 }; // Encoding for ":status: 200"
-
- HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders | FrameFlags.EndStream, 0, 0, 0, streamId);
- await WriteFrameAsync(headersFrame).ConfigureAwait(false);
- }
-
- public async Task SendResponseHeadersAsync(int streamId, bool endStream = true, HttpStatusCode statusCode = HttpStatusCode.OK, bool isTrailingHeader = false, IList<HttpHeaderData> headers = null)
- {
- // For now, only support headers that fit in a single frame
- byte[] headerBlock = new byte[Frame.MaxFrameLength];
- int bytesGenerated = 0;
-
- if (!isTrailingHeader)
- {
- string statusCodeString = ((int)statusCode).ToString();
- bytesGenerated += EncodeHeader(new HttpHeaderData(":status", statusCodeString), headerBlock.AsSpan());
- }
-
- if (headers != null)
- {
- foreach (HttpHeaderData headerData in headers)
- {
- bytesGenerated += EncodeHeader(headerData, headerBlock.AsSpan().Slice(bytesGenerated));
- }
- }
-
- FrameFlags flags = FrameFlags.EndHeaders;
- if (endStream)
- {
- flags |= FrameFlags.EndStream;
- }
-
- HeadersFrame headersFrame = new HeadersFrame(headerBlock.AsMemory().Slice(0, bytesGenerated), flags, 0, 0, 0, streamId);
- await WriteFrameAsync(headersFrame).ConfigureAwait(false);
- }
-
- public async Task SendResponseDataAsync(int streamId, ReadOnlyMemory<byte> responseData, bool endStream)
- {
- DataFrame dataFrame = new DataFrame(responseData, endStream ? FrameFlags.EndStream : FrameFlags.None, 0, streamId);
- await WriteFrameAsync(dataFrame).ConfigureAwait(false);
- }
-
- public async Task SendResponseBodyAsync(int streamId, ReadOnlyMemory<byte> responseBody)
- {
- // Only support response body if it fits in a single frame, for now
- // In the future we should separate the body into chunks as needed,
- // and if it's larger than the default window size, we will need to process window updates as well.
- if (responseBody.Length > Frame.MaxFrameLength)
- {
- throw new Exception("Response body too long");
- }
+ connection.ExpectSettingsAck();
- await SendResponseDataAsync(streamId, responseBody, true).ConfigureAwait(false);
+ return (connection, clientSettingsFrame);
}
public override void Dispose()
public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null)
{
- await EstablishConnectionAsync().ConfigureAwait(false);
+ Http2LoopbackConnection connection = await EstablishConnectionAsync().ConfigureAwait(false);
- (int streamId, HttpRequestData requestData) = await ReadAndParseRequestHeaderAsync().ConfigureAwait(false);
+ (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync().ConfigureAwait(false);
// We are about to close the connection, after we send the response.
// So, send a GOAWAY frame now so the client won't inadvertantly try to reuse the connection.
- await SendGoAway(streamId).ConfigureAwait(false);
+ await connection.SendGoAway(streamId).ConfigureAwait(false);
if (content == null)
{
- await SendResponseHeadersAsync(streamId, endStream: true, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
+ await connection.SendResponseHeadersAsync(streamId, endStream: true, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
}
else
{
- await SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
- await SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes(content)).ConfigureAwait(false);
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
+ await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes(content)).ConfigureAwait(false);
}
- await WaitForConnectionShutdownAsync().ConfigureAwait(false);
+ await connection.WaitForConnectionShutdownAsync().ConfigureAwait(false);
return requestData;
}
},
async server =>
{
- await server.EstablishConnectionAsync();
- (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync();
HttpHeaderData header = requestData.Headers.Single(x => x.Name == headerName);
Assert.Equal(expectedValue, header.Value);
Assert.True(expectedEncoding.AsSpan().SequenceEqual(header.Raw));
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
});
}
{
Task sendTask = client.GetAsync(server.Address);
- string connectionPreface = await server.AcceptConnectionAsync();
+ string connectionPreface = (await server.AcceptConnectionAsync()).PrefixString;
Assert.Equal("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", connectionPreface);
}
{
Task sendTask = client.GetAsync(server.Address);
- await server.AcceptConnectionAsync();
+ Http2LoopbackConnection connection = await server.AcceptConnectionAsync();
// Receive the initial client settings frame.
- Frame receivedFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Frame receivedFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(FrameType.Settings, receivedFrame.Type);
// Send the initial server settings frame.
Frame emptySettings = new Frame(0, FrameType.Settings, FrameFlags.None, 0);
- await server.WriteFrameAsync(emptySettings).ConfigureAwait(false);
+ await connection.WriteFrameAsync(emptySettings).ConfigureAwait(false);
// Receive the server settings frame ACK.
// This doesn't have to be the next frame, as the client is allowed to send before receiving our SETTINGS frame.
// So, loop until we see it (or the timeout expires)
while (true)
{
- receivedFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ receivedFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
if (receivedFrame.Type == FrameType.Settings && receivedFrame.AckFlag)
{
break;
{
Task sendTask = client.GetAsync(server.Address);
- await server.AcceptConnectionAsync();
+ Http2LoopbackConnection connection = await server.AcceptConnectionAsync();
// Send a frame despite not having sent the server connection preface.
DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.Padded, 10, 1);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
// Send zero-length body
var frame = new DataFrame(new byte[0], FrameFlags.EndStream, 0, streamId);
- await server.WriteFrameAsync(frame);
+ await connection.WriteFrameAsync(frame);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
// Send a bunch of valid SETTINGS values (that won't interfere with processing requests)
- await server.EstablishConnectionAsync(
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync(
new SettingsEntry { SettingId = SettingId.HeaderTableSize, Value = 0 },
new SettingsEntry { SettingId = SettingId.HeaderTableSize, Value = 1 },
new SettingsEntry { SettingId = SettingId.HeaderTableSize, Value = 345678 },
new SettingsEntry { SettingId = SettingId.MaxHeaderListSize, Value = 10000000 },
new SettingsEntry { SettingId = (SettingId)5678, Value = 1234 });
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
- await server.WriteFrameAsync(resetStream);
+ await connection.WriteFrameAsync(resetStream);
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Send response headers
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
- await server.WriteFrameAsync(resetStream);
+ await connection.WriteFrameAsync(resetStream);
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Send response headers and partial response body
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
DataFrame dataFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
- await server.WriteFrameAsync(dataFrame);
+ await connection.WriteFrameAsync(dataFrame);
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
- await server.WriteFrameAsync(resetStream);
+ await connection.WriteFrameAsync(resetStream);
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
}
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ await connection.ReadRequestHeaderAsync();
// Send a malformed frame (streamId is 0)
DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, 0);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ await connection.ReadRequestHeaderAsync();
// Send a data frame on stream 5, which is in the idle state.
DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, 5);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Send a headers frame on stream 5, which is in the idle state.
- await server.SendDefaultResponseHeadersAsync(5);
+ await connection.SendDefaultResponseHeadersAsync(5);
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
using (HttpClient client = CreateHttpClient())
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId));
+ await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId));
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
using (HttpClient client = CreateHttpClient())
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
+ await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId));
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
using (HttpClient client = CreateHttpClient())
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
using (HttpClient client = CreateHttpClient())
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
- await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
using (HttpClient client = CreateHttpClient())
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
- await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
+ await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId));
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
using (HttpClient client = CreateHttpClient())
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
- await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
- await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
+ await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
+ await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId));
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ await connection.ReadRequestHeaderAsync();
// Send a GoAway frame on stream 1.
GoAwayFrame invalidFrame = new GoAwayFrame(0, (int)ProtocolErrors.ENHANCE_YOUR_CALM, new byte[0], 1);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ }
+ }
+
+ [ConditionalFact(nameof(SupportsAlpn))]
+ public async Task GoAwayFrame_NewRequest_NewConnection()
+ {
+ using (var server = Http2LoopbackServer.CreateServer())
+ using (HttpClient client = CreateHttpClient())
+ {
+ server.AllowMultipleConnections = true;
+
+ Task<HttpResponseMessage> sendTask1 = client.GetAsync(server.Address);
+ Http2LoopbackConnection connection1 = await server.EstablishConnectionAsync();
+ int streamId1 = await connection1.ReadRequestHeaderAsync();
+
+ await connection1.SendGoAway(streamId1);
+
+ await connection1.SendDefaultResponseAsync(streamId1);
+ HttpResponseMessage response1 = await sendTask1;
+ Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
+
+ // New connection should be established after GOAWAY
+ Task<HttpResponseMessage> sendTask2 = client.GetAsync(server.Address);
+ Http2LoopbackConnection connection2 = await server.EstablishConnectionAsync();
+ int streamId2 = await connection2.ReadRequestHeaderAsync();
+ await connection2.SendDefaultResponseAsync(streamId2);
+ HttpResponseMessage response2 = await sendTask2;
+ Assert.Equal(HttpStatusCode.OK, response2.StatusCode);
+
+ await connection1.WaitForConnectionShutdownAsync();
+ await connection2.WaitForConnectionShutdownAsync();
}
}
{
Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ await connection.ReadRequestHeaderAsync();
// Send a malformed frame.
DataFrame invalidFrame = new DataFrame(new byte[Frame.MaxFrameLength + 1], FrameFlags.None, 0, 0);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.FRAME_SIZE_ERROR);
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- server.IgnoreWindowUpdates();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ connection.IgnoreWindowUpdates();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Send response and end stream.
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
DataFrame dataFrame = new DataFrame(new byte[10], FrameFlags.EndStream, 0, streamId);
- await server.WriteFrameAsync(dataFrame);
+ await connection.WriteFrameAsync(dataFrame);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Send a frame on the now-closed stream.
DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
if (!IsWinHttpHandler)
{
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Send empty response.
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Send a frame on the now-closed stream.
DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
if (!IsWinHttpHandler)
{
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Send empty response.
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Send a frame on the now-closed stream.
WindowUpdateFrame invalidFrame = new WindowUpdateFrame(1, streamId);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
// The client should close the connection.
- await server.WaitForConnectionShutdownAsync();
+ await connection.WaitForConnectionShutdownAsync();
}
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
- await server.SendDefaultResponseHeadersAsync(streamId);
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendDefaultResponseHeadersAsync(streamId);
// Send a reset stream frame so that stream 1 moves to a terminal state.
RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)error, streamId);
- await server.WriteFrameAsync(resetStream);
+ await connection.WriteFrameAsync(resetStream);
await AssertProtocolErrorAsync(sendTask, error);
// Send a frame on the now-closed stream.
DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
- await server.WriteFrameAsync(invalidFrame);
+ await connection.WriteFrameAsync(invalidFrame);
if (!IsWinHttpHandler)
{
// The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+ Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
}
}
}
- private static async Task<int> EstablishConnectionAndProcessOneRequestAsync(HttpClient client, Http2LoopbackServer server)
+ private static async Task<(int, Http2LoopbackConnection)> EstablishConnectionAndProcessOneRequestAsync(HttpClient client, Http2LoopbackServer server)
{
int streamId = -1;
// Establish connection and send initial request/response to ensure connection is available for subsequent use
+ Http2LoopbackConnection connection = null;
+
await new[]
{
Task.Run(async () =>
}),
Task.Run(async () =>
{
- await server.EstablishConnectionAsync();
- streamId = await server.ReadRequestHeaderAsync();
- await server.SendDefaultResponseAsync(streamId);
+ connection = await server.EstablishConnectionAsync();
+ streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendDefaultResponseAsync(streamId);
})
}.WhenAllOrAnyFailed(TestHelper.PassingTestTimeoutMilliseconds);
- return streamId;
+ return (streamId, connection);
}
[ConditionalFact(nameof(SupportsAlpn))]
using (var server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
{
- int streamId = await EstablishConnectionAndProcessOneRequestAsync(client, server);
+ (int streamId, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server);
// Send GOAWAY.
GoAwayFrame goAwayFrame = new GoAwayFrame(streamId, 0, new byte[0], 0);
- await server.WriteFrameAsync(goAwayFrame);
+ await connection.WriteFrameAsync(goAwayFrame);
// The client should close the connection.
- await server.WaitForConnectionShutdownAsync();
+ await connection.WaitForConnectionShutdownAsync();
// New request should cause a new connection
await EstablishConnectionAndProcessOneRequestAsync(client, server);
using (var server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
{
- await EstablishConnectionAndProcessOneRequestAsync(client, server);
+ (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server);
// Issue three requests
Task<HttpResponseMessage> sendTask1 = client.GetAsync(server.Address);
Task<HttpResponseMessage> sendTask3 = client.GetAsync(server.Address);
// Receive three requests
- int streamId1 = await server.ReadRequestHeaderAsync();
- int streamId2 = await server.ReadRequestHeaderAsync();
- int streamId3 = await server.ReadRequestHeaderAsync();
+ int streamId1 = await connection.ReadRequestHeaderAsync();
+ int streamId2 = await connection.ReadRequestHeaderAsync();
+ int streamId3 = await connection.ReadRequestHeaderAsync();
Assert.True(streamId1 < streamId2);
Assert.True(streamId2 < streamId3);
// First response: Don't send anything yet
// Second response: Send headers, no body yet
- await server.SendDefaultResponseHeadersAsync(streamId2);
+ await connection.SendDefaultResponseHeadersAsync(streamId2);
// Third response: Send headers, partial body
- await server.SendDefaultResponseHeadersAsync(streamId3);
- await server.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
+ await connection.SendDefaultResponseHeadersAsync(streamId3);
+ await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
// Send a GOAWAY frame that indicates that we will process all three streams
GoAwayFrame goAwayFrame = new GoAwayFrame(streamId3, 0, new byte[0], 0);
- await server.WriteFrameAsync(goAwayFrame);
+ await connection.WriteFrameAsync(goAwayFrame);
// Finish sending responses
- await server.SendDefaultResponseHeadersAsync(streamId1);
- await server.SendResponseDataAsync(streamId1, new byte[10], endStream: true);
- await server.SendResponseDataAsync(streamId2, new byte[10], endStream: true);
- await server.SendResponseDataAsync(streamId3, new byte[5], endStream: true);
+ await connection.SendDefaultResponseHeadersAsync(streamId1);
+ await connection.SendResponseDataAsync(streamId1, new byte[10], endStream: true);
+ await connection.SendResponseDataAsync(streamId2, new byte[10], endStream: true);
+ await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: true);
// We will not send any more frames, so send EOF now, and ensure the client handles this properly.
- server.ShutdownSend();
+ connection.ShutdownSend();
// Receive all responses
HttpResponseMessage response1 = await sendTask1;
Assert.Equal(10, (await response3.Content.ReadAsByteArrayAsync()).Length);
// Now that all pending responses have been sent, the client should close the connection.
- await server.WaitForConnectionShutdownAsync();
+ await connection.WaitForConnectionShutdownAsync();
// New request should cause a new connection
await EstablishConnectionAndProcessOneRequestAsync(client, server);
using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
{
- await EstablishConnectionAndProcessOneRequestAsync(client, server);
+ (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server);
// Issue three requests
Task<HttpResponseMessage> sendTask1 = client.GetAsync(server.Address);
Task<HttpResponseMessage> sendTask3 = client.GetAsync(server.Address);
// Receive three requests
- int streamId1 = await server.ReadRequestHeaderAsync();
- int streamId2 = await server.ReadRequestHeaderAsync();
- int streamId3 = await server.ReadRequestHeaderAsync();
+ int streamId1 = await connection.ReadRequestHeaderAsync();
+ int streamId2 = await connection.ReadRequestHeaderAsync();
+ int streamId3 = await connection.ReadRequestHeaderAsync();
Assert.InRange(streamId1, int.MinValue, streamId2 - 1);
Assert.InRange(streamId2, int.MinValue, streamId3 - 1);
// First response: Don't send anything yet
// Second response: Send headers, no body yet
- await server.SendDefaultResponseHeadersAsync(streamId2);
+ await connection.SendDefaultResponseHeadersAsync(streamId2);
// Third response: Send headers, partial body
- await server.SendDefaultResponseHeadersAsync(streamId3);
- await server.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
+ await connection.SendDefaultResponseHeadersAsync(streamId3);
+ await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
// Send a GOAWAY frame that indicates that we will abort all the requests.
var goAwayFrame = new GoAwayFrame(0, (int)ProtocolErrors.ENHANCE_YOUR_CALM, new byte[0], 0);
- await server.WriteFrameAsync(goAwayFrame);
+ await connection.WriteFrameAsync(goAwayFrame);
// We will not send any more frames, so send EOF now, and ensure the client handles this properly.
- server.ShutdownSend();
+ connection.ShutdownSend();
await AssertProtocolErrorAsync(sendTask1, ProtocolErrors.ENHANCE_YOUR_CALM);
await AssertProtocolErrorAsync(sendTask2, ProtocolErrors.ENHANCE_YOUR_CALM);
await AssertProtocolErrorAsync(sendTask3, ProtocolErrors.ENHANCE_YOUR_CALM);
// Now that all pending responses have been sent, the client should close the connection.
- await server.WaitForConnectionShutdownAsync();
+ await connection.WaitForConnectionShutdownAsync();
// New request should cause a new connection
await EstablishConnectionAndProcessOneRequestAsync(client, server);
}
}
- private static async Task<int> ReadToEndOfStream(Http2LoopbackServer server, int streamId)
+ private static async Task<int> ReadToEndOfStream(Http2LoopbackConnection connection, int streamId)
{
int bytesReceived = 0;
while (true)
{
- Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(streamId, frame.StreamId);
Assert.Equal(FrameType.Data, frame.Type);
{
Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
int streamId = frame.StreamId;
Assert.Equal(FrameType.Headers, frame.Type);
Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
int bytesReceived = 0;
while (bytesReceived < InitialWindowSize)
{
- frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(streamId, frame.StreamId);
Assert.Equal(FrameType.Data, frame.Type);
Assert.Equal(FrameFlags.None, frame.Flags);
Assert.Equal(InitialWindowSize, bytesReceived);
// Issue another read. It shouldn't complete yet. Wait a brief period of time to ensure it doesn't complete.
- Task<Frame> readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Task<Frame> readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase connection window by one. This should still not complete the read.
- await server.WriteFrameAsync(new WindowUpdateFrame(1, 0));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(1, 0));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase stream window by two. This should complete the read with a single byte.
- await server.WriteFrameAsync(new WindowUpdateFrame(2, streamId));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(2, streamId));
frame = await readFrameTask;
Assert.Equal(1, frame.Length);
bytesReceived++;
// Issue another read and ensure it doesn't complete yet.
- readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase connection window by two. This should complete the read with a single byte.
- await server.WriteFrameAsync(new WindowUpdateFrame(2, 0));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(2, 0));
frame = await readFrameTask;
Assert.Equal(1, frame.Length);
bytesReceived++;
// Issue another read and ensure it doesn't complete yet.
- readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase connection window to allow exactly the remaining request size. This should still not complete the read.
- await server.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived - 1, 0));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived - 1, 0));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase stream window to allow exactly the remaining request size. This should allow the rest of the request to be sent.
- await server.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived, streamId));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived, streamId));
frame = await readFrameTask;
Assert.Equal(streamId, frame.StreamId);
bytesReceived += frame.Length;
// Read to end of stream
- bytesReceived += await ReadToEndOfStream(server, streamId);
+ bytesReceived += await ReadToEndOfStream(connection, streamId);
Assert.Equal(ContentSize, bytesReceived);
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await clientTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
{
Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
// Bump connection window so it won't block the client.
- await server.WriteFrameAsync(new WindowUpdateFrame(ContentSize - DefaultInitialWindowSize, 0));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(ContentSize - DefaultInitialWindowSize, 0));
- Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
int streamId = frame.StreamId;
Assert.Equal(FrameType.Headers, frame.Type);
Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
int bytesReceived = 0;
while (bytesReceived < DefaultInitialWindowSize)
{
- frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(streamId, frame.StreamId);
Assert.Equal(FrameType.Data, frame.Type);
Assert.Equal(FrameFlags.None, frame.Flags);
Assert.Equal(DefaultInitialWindowSize, bytesReceived);
// Issue another read. It shouldn't complete yet. Wait a brief period of time to ensure it doesn't complete.
- Task<Frame> readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Task<Frame> readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Change SETTINGS_INITIAL_WINDOW_SIZE to 0. This will make the client's credit go negative.
- server.ExpectSettingsAck();
- await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 0 }));
+ connection.ExpectSettingsAck();
+ await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 0 }));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase stream window by one. Client credit will still be negative.
- await server.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Change SETTINGS_INITIAL_WINDOW_SIZE to 1. Client credit will still be negative.
- server.ExpectSettingsAck();
- await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 1 }));
+ connection.ExpectSettingsAck();
+ await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 1 }));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase stream window so client credit will be 0.
- await server.WriteFrameAsync(new WindowUpdateFrame(DefaultInitialWindowSize - 2, streamId));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(DefaultInitialWindowSize - 2, streamId));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase stream window by one, so client can now send a single byte.
- await server.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
frame = await readFrameTask;
Assert.Equal(FrameType.Data, frame.Type);
bytesReceived++;
// Issue another read and ensure it doesn't complete yet.
- readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase SETTINGS_INITIAL_WINDOW_SIZE to 2, so client can now send a single byte.
- server.ExpectSettingsAck();
- await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 2 }));
+ connection.ExpectSettingsAck();
+ await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 2 }));
frame = await readFrameTask;
Assert.Equal(FrameType.Data, frame.Type);
bytesReceived++;
// Issue another read and ensure it doesn't complete yet.
- readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Increase SETTINGS_INITIAL_WINDOW_SIZE to be enough that the client can send the rest of the content.
- server.ExpectSettingsAck();
- await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = ContentSize - (DefaultInitialWindowSize - 1) }));
+ connection.ExpectSettingsAck();
+ await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = ContentSize - (DefaultInitialWindowSize - 1) }));
frame = await readFrameTask;
Assert.Equal(streamId, frame.StreamId);
bytesReceived += frame.Length;
// Read to end of stream
- bytesReceived += await ReadToEndOfStream(server, streamId);
+ bytesReceived += await ReadToEndOfStream(connection, streamId);
Assert.Equal(ContentSize, bytesReceived);
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await clientTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- server.IgnoreWindowUpdates();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ connection.IgnoreWindowUpdates();
// Process first request and send response.
- int streamId = await server.ReadRequestHeaderAsync();
- await server.SendDefaultResponseAsync(streamId);
+ int streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Change MaxConcurrentStreams setting and wait for ack.
// (We don't want to send any new requests until we receive the ack, otherwise we may have a timing issue.)
SettingsFrame settingsFrame = new SettingsFrame(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 0 });
- await server.WriteFrameAsync(settingsFrame);
- Frame settingsAckFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ await connection.WriteFrameAsync(settingsFrame);
+ Frame settingsAckFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(FrameType.Settings, settingsAckFrame.Type);
Assert.Equal(FrameFlags.Ack, settingsAckFrame.Flags);
Task<HttpResponseMessage> sendTask2 = client.GetAsync(server.Address);
// Issue another read. It shouldn't complete yet. Wait a brief period of time to ensure it doesn't complete.
- Task<Frame> readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Task<Frame> readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Change MaxConcurrentStreams again to allow a single request to come through.
- server.ExpectSettingsAck();
+ connection.ExpectSettingsAck();
settingsFrame = new SettingsFrame(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 1 });
- await server.WriteFrameAsync(settingsFrame);
+ await connection.WriteFrameAsync(settingsFrame);
// First request should be sent
Frame frame = await readFrameTask;
streamId = frame.StreamId;
// Issue another read. Second request should not be sent yet.
- readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
await Task.Delay(500);
Assert.False(readFrameTask.IsCompleted);
// Send response for first request
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
streamId = frame.StreamId;
// Send response for second request
- await server.SendDefaultResponseAsync(streamId);
+ await connection.SendDefaultResponseAsync(streamId);
response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- server.IgnoreWindowUpdates();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ connection.IgnoreWindowUpdates();
// Process first request and send response.
- int streamId = await server.ReadRequestHeaderAsync();
- await server.SendDefaultResponseAsync(streamId);
+ int streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendDefaultResponseAsync(streamId);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Change MaxConcurrentStreams setting and wait for ack.
// (We don't want to send any new requests until we receive the ack, otherwise we may have a timing issue.)
SettingsFrame settingsFrame = new SettingsFrame(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 0 });
- await server.WriteFrameAsync(settingsFrame);
- Frame settingsAckFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ await connection.WriteFrameAsync(settingsFrame);
+ Frame settingsAckFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(FrameType.Settings, settingsAckFrame.Type);
Assert.Equal(FrameFlags.Ack, settingsAckFrame.Flags);
var cts = new CancellationTokenSource();
Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content, cts.Token);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
int streamId = frame.StreamId;
Assert.Equal(FrameType.Headers, frame.Type);
Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
int bytesReceived = 0;
while (bytesReceived < InitialWindowSize)
{
- frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(streamId, frame.StreamId);
Assert.Equal(FrameType.Data, frame.Type);
Assert.Equal(FrameFlags.None, frame.Flags);
Assert.True(stopwatch.ElapsedMilliseconds < 30000);
// The server should receive a RstStream frame.
- frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
Assert.Equal(FrameType.RstStream, frame.Type);
}
}
Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content, cts.Token);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+ Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
int streamId = frame.StreamId;
Assert.Equal(FrameType.Headers, frame.Type);
Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
// Increase the size of the HTTP/2 Window, so that it is large enough to fill the
// TCP window when we do not perform any reads on the server side.
- await server.WriteFrameAsync(new WindowUpdateFrame(InitialWindowSize, streamId));
+ await connection.WriteFrameAsync(new WindowUpdateFrame(InitialWindowSize, streamId));
// Give the client time to read the window update frame, and for the write to pend.
await Task.Delay(1000);
},
async server =>
{
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync(readBody : false);
+ (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody : false);
Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
if (send100Continue)
{
- await server.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Continue);
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Continue);
}
- await server.ReadBodyAsync();
- await server.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK);
- await server.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("OK"));
- await server.SendGoAway(streamId);
- await server.WaitForConnectionShutdownAsync();
+ await connection.ReadBodyAsync();
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK);
+ await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("OK"));
+ await connection.SendGoAway(streamId);
+ await connection.WaitForConnectionShutdownAsync();
});
}
},
async server =>
{
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync(readBody : false);
+ (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody : false);
Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
// Wait for client so start sending body.
await tsc.Task.ConfigureAwait(false);
// And reject content with 403.
- await server.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Forbidden);
- await server.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("no no!"));
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Forbidden);
+ await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("no no!"));
try
{
// Client should send reset.
- await server.ReadBodyAsync();
+ await connection.ReadBodyAsync();
Assert.True(false, "Should not be here");
}
catch (IOException) { };
- await server.SendGoAway(streamId);
- await server.WaitForConnectionShutdownAsync();
+ await connection.SendGoAway(streamId);
+ await connection.WaitForConnectionShutdownAsync();
});
}
},
async server =>
{
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync(readBody : false);
+ (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody : false);
// Wait for client so start sending body.
await tsc.Task.ConfigureAwait(false);
if (shouldWait)
{
// Read body first before sending back response
- await server.ReadBodyAsync();
+ await connection.ReadBodyAsync();
}
- await server.SendResponseHeadersAsync(streamId, endStream: false, responseCode);
- await server.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes(responseContent), endStream: false);
+ await connection.SendResponseHeadersAsync(streamId, endStream: false, responseCode);
+ await connection.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes(responseContent), endStream: false);
if (!shouldWait)
{
try
{
// Client should send reset.
- await server.ReadBodyAsync();
+ await connection.ReadBodyAsync();
if (responseCode != HttpStatusCode.OK) Assert.True(false, "Should not be here");
}
catch (IOException) when (responseCode != HttpStatusCode.OK) { };
}
var headers = new HttpHeaderData[] { new HttpHeaderData("x-last", "done") };
- await server.SendResponseHeadersAsync(streamId, endStream: true, isTrailingHeader : true, headers: headers);
- await server.SendGoAway(streamId);
- await server.WaitForConnectionShutdownAsync();
+ await connection.SendResponseHeadersAsync(streamId, endStream: true, isTrailingHeader : true, headers: headers);
+ await connection.SendGoAway(streamId);
+ await connection.WaitForConnectionShutdownAsync();
});
}
IList<HttpHeaderData> headers = new HttpHeaderData[] { new HttpHeaderData(":status", "300"), new HttpHeaderData("x-test", "Http2GetAsync_MultipleStatusHeaders_Throws") };
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
- await server.SendResponseHeadersAsync(streamId, endStream : true, headers: headers);
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendResponseHeadersAsync(streamId, endStream : true, headers: headers);
await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
}
}
IList<HttpHeaderData> headers = new HttpHeaderData[] { new HttpHeaderData("x-test", "Http2GetAsync_StatusHeaderNotFirst_Throws"), new HttpHeaderData(":status", "200") };
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
- await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
}
IList<HttpHeaderData> headers = new HttpHeaderData[] { new HttpHeaderData(":path", "http"), new HttpHeaderData("x-test", "Http2GetAsync_TrailigPseudo_Throw") };
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
- await server.SendDefaultResponseHeadersAsync(streamId);
- await server.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes("hello"), endStream: false);
- await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes("hello"), endStream: false);
+ await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
}
{
try
{
- SettingsFrame clientSettings = await server.EstablishConnectionAsync();
+ (Http2LoopbackConnection connection, SettingsFrame clientSettings) = await server.EstablishConnectionGetSettingsAsync();
SettingsEntry clientWindowSizeSetting = clientSettings.Entries.SingleOrDefault(x => x.SettingId == SettingId.InitialWindowSize);
int clientWindowSize = clientWindowSizeSetting.SettingId == SettingId.InitialWindowSize ? (int)clientWindowSizeSetting.Value : 65535;
// Exceed the window size by 1 byte.
++clientWindowSize;
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Write the response.
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
byte[] buffer = new byte[4096];
int totalSent = 0;
int sendSize = Math.Min(buffer.Length, clientWindowSize - totalSent);
ReadOnlyMemory<byte> sendBuf = buffer.AsMemory(0, sendSize);
- await server.SendResponseDataAsync(streamId, sendBuf, endStream: false);
+ await connection.SendResponseDataAsync(streamId, sendBuf, endStream: false);
totalSent += sendSize;
}
// If client is misbehaving, we'll get an OperationCanceledException due to timeout.
try
{
- Frame clientFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(5));
+ Frame clientFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(5));
Assert.True(clientFrame == null || (clientFrame.Type == FrameType.RstStream && clientFrame.StreamId == streamId),
"Unexpected frame received from HttpClient; Expected either RST_STREAM or connection reset.");
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
// Response data.
- await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes, endStream: true));
+ await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes, endStream: true));
// Server doesn't send trailing header frame.
HttpResponseMessage response = await sendTask;
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
// Response data, missing Trailers.
- await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+ await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
// Additional trailing header frame.
- await server.SendResponseHeadersAsync(streamId, isTrailingHeader:true, headers: TrailingHeaders, endStream : true);
+ await connection.SendResponseHeadersAsync(streamId, isTrailingHeader:true, headers: TrailingHeaders, endStream : true);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await server.SendDefaultResponseHeadersAsync(streamId);
- await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+ await connection.SendDefaultResponseHeadersAsync(streamId);
+ await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
// Additional trailing header frame with pseudo-headers again..
- await server.SendResponseHeadersAsync(streamId, isTrailingHeader:false, headers: TrailingHeaders, endStream : true);
+ await connection.SendResponseHeadersAsync(streamId, isTrailingHeader:false, headers: TrailingHeaders, endStream : true);
await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
}
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address, HttpCompletionOption.ResponseHeadersRead);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
// Response data, missing Trailers.
- await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+ await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Empty(response.TrailingHeaders);
// Finish data stream and write out trailing headers.
- await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
- await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
+ await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+ await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
// Read data until EOF is reached
while (stream.Read(data, 0, data.Length) != 0);
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await server.SendDefaultResponseHeadersAsync(streamId);
- await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
// Response header.
- await server.SendDefaultResponseHeadersAsync(streamId);
+ await connection.SendDefaultResponseHeadersAsync(streamId);
// No data.
// Response trailing headers
- await server.SendResponseHeadersAsync(streamId, isTrailingHeader: true, headers: TrailingHeaders);
+ await connection.SendResponseHeadersAsync(streamId, isTrailingHeader: true, headers: TrailingHeaders);
HttpResponseMessage response = await sendTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
SetDefaultRequestVersion(client, HttpVersion.Version20);
Task<string> request1 = client.GetStringAsync(url);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
- await server.SendDefaultResponseAsync(streamId);
+ Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+ int streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendDefaultResponseAsync(streamId);
await request1;
// Wait a small amount of time before making the second request, to give the first request time to timeout.
await Task.Delay(100);
// Grab reference to underlying socket and stream to make sure they are not disposed and closed.
- (Socket socket, Stream stream) = server.ResetNetwork();
+ (Socket socket, Stream stream) = connection.ResetNetwork();
// Make second request and expect it to be served from a different connection.
Task<string> request2 = client.GetStringAsync(url);
- await server.EstablishConnectionAsync();
- streamId = await server.ReadRequestHeaderAsync();
- await server.SendDefaultResponseAsync(streamId);
+ connection = await server.EstablishConnectionAsync();
+ streamId = await connection.ReadRequestHeaderAsync();
+ await connection.SendDefaultResponseAsync(streamId);
await request2;
// Close underlying socket from first connection.
<Compile Include="$(CommonTestPath)\System\Net\Http\Http2LoopbackServer.cs">
<Link>Common\System\Net\Http\Http2LoopbackServer.cs</Link>
</Compile>
+ <Compile Include="$(CommonTestPath)\System\Net\Http\Http2LoopbackConnection.cs">
+ <Link>Common\System\Net\Http\Http2LoopbackConnection.cs</Link>
+ </Compile>
<Compile Include="$(CommonTestPath)\System\Net\Http\HuffmanDecoder.cs">
<Link>Common\System\Net\Http\HuffmanDecoder.cs</Link>
</Compile>