Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP3: Re-enable cookie and cancellation tests #54727

Merged
merged 11 commits into from
Jun 26, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,13 @@ public abstract class GenericLoopbackConnection : IDisposable
/// <summary>Read complete request body if not done by ReadRequestData.</summary>
public abstract Task<Byte[]> ReadRequestBodyAsync();

/// <summary>Sends Response back with provided statusCode, headers and content. Can be called multiple times on same response if isFinal was set to false before.</summary>
public abstract Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0);
/// <summary>Sends Response back with provided statusCode, headers and content.
/// If isFinal is false, the body is not completed and you can call SendResponseBodyAsync to send more.</summary>
public abstract Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0);
/// <summary>Sends response headers.</summary>
public abstract Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0);
/// <summary>Sends valid but incomplete headers. Once called, there is no way to continue the response past this point.</summary>
public abstract Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0);
/// <summary>Sends Response body after SendResponse was called with isFinal: false.</summary>
public abstract Task SendResponseBodyAsync(byte[] content, bool isFinal = true, int requestId = 0);

Expand Down Expand Up @@ -184,7 +187,7 @@ public class HttpRequestData
public string Path;
public Version Version;
public List<HttpHeaderData> Headers { get; }
public int RequestId; // Generic request ID. Currently only used for HTTP/2 to hold StreamId.
public int RequestId; // HTTP/2 StreamId.

public HttpRequestData()
{
Expand Down Expand Up @@ -246,5 +249,7 @@ public int GetHeaderValueCount(string headerName)
{
return Headers.Where(h => h.Name.Equals(headerName, StringComparison.OrdinalIgnoreCase)).Count();
}

public override string ToString() => $"{Method} {Path} HTTP/{Version}\r\n{string.Join("\r\n", Headers)}\r\n\r\n";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -845,11 +845,8 @@ public override Task<Byte[]> ReadRequestBodyAsync()
return ReadBodyAsync();
}

public override async Task SendResponseAsync(HttpStatusCode? statusCode = null, IList<HttpHeaderData> headers = null, string body = null, bool isFinal = true, int requestId = 0)
public override async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
geoffkizer marked this conversation as resolved.
Show resolved Hide resolved
{
// TODO: Header continuation support.
Assert.NotNull(statusCode);

if (headers != null)
{
bool hasDate = false;
Expand Down Expand Up @@ -886,16 +883,15 @@ public override async Task SendResponseAsync(HttpStatusCode? statusCode = null,
}

int streamId = requestId == 0 ? _lastStreamId : requestId;
bool endHeaders = body != null || isFinal;

if (string.IsNullOrEmpty(body))
if (string.IsNullOrEmpty(content))
{
await SendResponseHeadersAsync(streamId, endStream: isFinal, (HttpStatusCode)statusCode, endHeaders: endHeaders, headers: headers);
await SendResponseHeadersAsync(streamId, endStream: isFinal, (HttpStatusCode)statusCode, endHeaders: true, headers: headers);
}
else
{
await SendResponseHeadersAsync(streamId, endStream: false, (HttpStatusCode)statusCode, endHeaders: endHeaders, headers: headers);
await SendResponseBodyAsync(body, isFinal: isFinal, requestId: streamId);
await SendResponseHeadersAsync(streamId, endStream: false, (HttpStatusCode)statusCode, endHeaders: true, headers: headers);
await SendResponseBodyAsync(content, isFinal: isFinal, requestId: streamId);
}
}

Expand All @@ -905,10 +901,16 @@ public override Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpSt
return SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, endHeaders: true, headers);
}

public override Task SendResponseBodyAsync(byte[] body, bool isFinal = true, int requestId = 0)
public override Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
{
int streamId = requestId == 0 ? _lastStreamId : requestId;
return SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, endHeaders: false, headers);
}

public override Task SendResponseBodyAsync(byte[] content, bool isFinal = true, int requestId = 0)
{
int streamId = requestId == 0 ? _lastStreamId : requestId;
return SendResponseBodyAsync(streamId, body, isFinal);
return SendResponseBodyAsync(streamId, content, isFinal);
}

public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal sealed class Http3LoopbackConnection : GenericLoopbackConnection

private readonly QuicConnection _connection;
private readonly Dictionary<int, Http3LoopbackStream> _openStreams = new Dictionary<int, Http3LoopbackStream>();
private Http3LoopbackStream _controlStream; // Our outbound control stream
private Http3LoopbackStream _currentStream;
private bool _closed;

Expand Down Expand Up @@ -138,6 +139,13 @@ public async Task<Http3LoopbackStream> AcceptRequestStreamAsync()
}
}

public async Task EstablishControlStreamAsync()
{
_controlStream = OpenUnidirectionalStream();
await _controlStream.SendUnidirectionalStreamTypeAsync(Http3LoopbackStream.ControlStream);
await _controlStream.SendSettingsFrameAsync();
}

public override async Task<byte[]> ReadRequestBodyAsync()
{
return await _currentStream.ReadRequestBodyAsync().ConfigureAwait(false);
Expand All @@ -149,7 +157,7 @@ public override async Task<HttpRequestData> ReadRequestDataAsync(bool readBody =
return await stream.ReadRequestDataAsync(readBody).ConfigureAwait(false);
}

public override Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
public override Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true, int requestId = 0)
{
return GetOpenRequest(requestId).SendResponseAsync(statusCode, headers, content, isFinal);
}
Expand All @@ -164,12 +172,25 @@ public override Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpSt
return GetOpenRequest(requestId).SendResponseHeadersAsync(statusCode, headers);
}

public override Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, int requestId = 0)
{
return GetOpenRequest(requestId).SendPartialResponseHeadersAsync(statusCode, headers);
}

public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
Http3LoopbackStream stream = await AcceptRequestStreamAsync().ConfigureAwait(false);
HttpRequestData request = await stream.HandleRequestAsync(statusCode, headers, content);

HttpRequestData request = await stream.ReadRequestDataAsync().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 _controlStream.SendGoAwayFrameAsync(stream.StreamId + 4);

await stream.SendResponseAsync(statusCode, headers, content).ConfigureAwait(false);

// closing the connection here causes bytes written to streams to go missing.
// Regardless, we told the client we are closing so it shouldn't matter -- they should not use this connection anymore.
//await CloseAsync(H3_NO_ERROR).ConfigureAwait(false);

return request;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ public override void Dispose()
public override async Task<GenericLoopbackConnection> EstablishGenericConnectionAsync()
{
QuicConnection con = await _listener.AcceptConnectionAsync().ConfigureAwait(false);
return new Http3LoopbackConnection(con);
Http3LoopbackConnection connection = new Http3LoopbackConnection(con);

await connection.EstablishControlStreamAsync();
return connection;
}

public override async Task AcceptConnectionAsync(Func<GenericLoopbackConnection, Task> funcAsync)
Expand Down
69 changes: 58 additions & 11 deletions src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal sealed class Http3LoopbackStream : IDisposable
private const long DataFrame = 0x0;
private const long HeadersFrame = 0x1;
private const long SettingsFrame = 0x4;
private const long GoAwayFrame = 0x7;

public const long ControlStream = 0x0;
public const long PushStream = 0x1;
Expand All @@ -43,6 +44,9 @@ public void Dispose()
{
_stream.Dispose();
}

public long StreamId => _stream.StreamId;

public async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
HttpRequestData request = await ReadRequestDataAsync().ConfigureAwait(false);
Expand All @@ -57,8 +61,10 @@ public async Task SendUnidirectionalStreamTypeAsync(long streamType)
await _stream.WriteAsync(buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
}

public async Task SendSettingsFrameAsync(ICollection<(long settingId, long settingValue)> settings)
public async Task SendSettingsFrameAsync(ICollection<(long settingId, long settingValue)> settings = null)
{
settings ??= Array.Empty<(long settingId, long settingValue)>();

var buffer = new byte[settings.Count * MaximumVarIntBytes * 2];

int bytesWritten = 0;
Expand All @@ -72,7 +78,7 @@ public async Task SendSettingsFrameAsync(ICollection<(long settingId, long setti
await SendFrameAsync(SettingsFrame, buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
}

public async Task SendHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
private Memory<byte> ConstructHeadersPayload(IEnumerable<HttpHeaderData> headers)
{
int bufferLength = QPackTestEncoder.MaxPrefixLength;

Expand All @@ -95,24 +101,56 @@ public async Task SendHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
bytesWritten += QPackTestEncoder.EncodeHeader(buffer.AsSpan(bytesWritten), header.Name, header.Value, header.ValueEncoding, header.HuffmanEncoded ? QPackFlags.HuffmanEncode : QPackFlags.None);
}

await SendFrameAsync(HeadersFrame, buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
return buffer.AsMemory(0, bytesWritten);
}

private async Task SendHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
{
await SendFrameAsync(HeadersFrame, ConstructHeadersPayload(headers)).ConfigureAwait(false);
}

private async Task SendPartialHeadersFrameAsync(IEnumerable<HttpHeaderData> headers)
{
Memory<byte> payload = ConstructHeadersPayload(headers);

await SendFrameHeaderAsync(HeadersFrame, payload.Length);

// Slice off final byte so the payload is not complete
payload = payload.Slice(0, payload.Length - 1);

await _stream.WriteAsync(payload).ConfigureAwait(false);
}

public async Task SendDataFrameAsync(ReadOnlyMemory<byte> data)
{
await SendFrameAsync(DataFrame, data).ConfigureAwait(false);
}

public async Task SendFrameAsync(long frameType, ReadOnlyMemory<byte> framePayload)
// Note that unlike HTTP2, the stream ID here indicates the *first invalid* stream.
public async Task SendGoAwayFrameAsync(long firstInvalidStreamId)
{
var buffer = new byte[QPackTestEncoder.MaxVarIntLength];
int bytesWritten = 0;

bytesWritten += EncodeHttpInteger(firstInvalidStreamId, buffer);
await SendFrameAsync(GoAwayFrame, buffer.AsMemory(0, bytesWritten));
}

private async Task SendFrameHeaderAsync(long frameType, int payloadLength)
{
var buffer = new byte[MaximumVarIntBytes * 2];

int bytesWritten = 0;

bytesWritten += EncodeHttpInteger(frameType, buffer.AsSpan(bytesWritten));
bytesWritten += EncodeHttpInteger(framePayload.Length, buffer.AsSpan(bytesWritten));
bytesWritten += EncodeHttpInteger(payloadLength, buffer.AsSpan(bytesWritten));

await _stream.WriteAsync(buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
}

public async Task SendFrameAsync(long frameType, ReadOnlyMemory<byte> framePayload)
{
await SendFrameHeaderAsync(frameType, framePayload.Length).ConfigureAwait(false);
await _stream.WriteAsync(framePayload).ConfigureAwait(false);
}

Expand Down Expand Up @@ -189,7 +227,7 @@ public async Task<HttpRequestData> ReadRequestDataAsync(bool readBody = true)
return requestData;
}

public async Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true)
public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "", bool isFinal = true)
{
IEnumerable<HttpHeaderData> newHeaders = headers ?? Enumerable.Empty<HttpHeaderData>();

Expand All @@ -202,21 +240,30 @@ public async Task SendResponseAsync(HttpStatusCode? statusCode = HttpStatusCode.
await SendResponseBodyAsync(Encoding.UTF8.GetBytes(content ?? ""), isFinal).ConfigureAwait(false);
}

public async Task SendResponseHeadersAsync(HttpStatusCode? statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
private IEnumerable<HttpHeaderData> PrepareHeaders(HttpStatusCode statusCode, IEnumerable<HttpHeaderData> headers)
{
headers ??= Enumerable.Empty<HttpHeaderData>();

// Some tests use Content-Length with a null value to indicate Content-Length should not be set.
headers = headers.Where(x => x.Name != "Content-Length" || x.Value != null);

if (statusCode != null)
{
headers = headers.Prepend(new HttpHeaderData(":status", ((int)statusCode).ToString(CultureInfo.InvariantCulture)));
}
headers = headers.Prepend(new HttpHeaderData(":status", ((int)statusCode).ToString(CultureInfo.InvariantCulture)));

return headers;
}

public async Task SendResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
{
headers = PrepareHeaders(statusCode, headers);
await SendHeadersFrameAsync(headers).ConfigureAwait(false);
}

public async Task SendPartialResponseHeadersAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IEnumerable<HttpHeaderData> headers = null)
{
headers = PrepareHeaders(statusCode, headers);
await SendPartialHeadersFrameAsync(headers).ConfigureAwait(false);
}

public async Task SendResponseBodyAsync(byte[] content, bool isFinal = true)
{
if (content?.Length != 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
Task serverTask = server.AcceptConnectionAsync(async connection =>
{
await connection.ReadRequestDataAsync();
await connection.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal: false);
await connection.SendPartialResponseHeadersAsync(HttpStatusCode.OK);

partialResponseHeadersSent.TrySetResult(true);
await clientFinished.Task;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,7 @@ await LoopbackServerFactory.CreateServerAsync(async (server3, url3) =>
Task serverTask2 = server2.AcceptConnectionAsync(async connection2 =>
{
await connection2.ReadRequestDataAsync();
await connection2.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal : false);
await connection2.SendPartialResponseHeadersAsync(HttpStatusCode.OK);
await unblockServers.Task;
});

Expand Down
Loading