Skip to content

Commit

Permalink
[QUIC] API QuicConnection (#71783), QuicStream (#71969), QUIC public (#…
Browse files Browse the repository at this point in the history
…72031) (#72106)

* [QUIC] API QuicConnection (#71783)

* Listener comment; PreviewFeature attribute

* Feedback

* QuicConnection new API including compilable implementation

* Fixed logging

* Fixed S.N.Quic and S.N.Http tests

* Options now correspond to the issue

* Feedback

* Comments, PreviewFeature attribute and RemoteCertificate disposal.

* Preview feature attribute is assembly wide

* Some typos

* Fixed test with certificate

* Default values as constants

* Event handlers split into methods called via switch expression.

* Some more comments

* Unified unsafe usage

* Fixed some more tests

* Cleaned up some exceptions and resource strings.

* Feedback

* Latest greatest API proposal.

* Fixed Http solution

* Feedback

* [QUIC] API QuicStream (#71969)

* Quic stream API surface

* Fixed test compilation

* Fixed http test compilation

* HttpLoopbackConnection Dispose -> DisposeAsync

* QuicStream implementation

* Fixed some tests

* Fixed all QUIC and HTTP tests

* Fixed exception type for stream closed by connection close

* Feedback

* Fixed WebSocket.Client test build

* Feedback, test fixes

* Fixed build on framework and windows

* Fixed winhandler test

* Swap variable based on order in defining class

* Post merge fixes

* Feedback and build

* Reverted connection state to pass around abort error code

* Fixed exception type.

* [QUIC] System.Net.Quic API made public (#72031)

* System.Net.Quic removed from ASP transport package and made part of SDK ref

* Removed manual references to System.Net.Quic.csproj
  • Loading branch information
ManickaP authored Jul 13, 2022
1 parent 9cc6254 commit 060de1c
Show file tree
Hide file tree
Showing 72 changed files with 3,133 additions and 4,119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ private void CloseWebSocket()
}
}

public abstract class GenericLoopbackConnection : IDisposable
public abstract class GenericLoopbackConnection : IAsyncDisposable
{
public abstract void Dispose();
public abstract ValueTask DisposeAsync();

public abstract Task InitializeConnectionAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,12 +838,12 @@ public async Task SendResponseBodyAsync(int streamId, ReadOnlyMemory<byte> respo
await SendResponseDataAsync(streamId, responseBody, isFinal).ConfigureAwait(false);
}

public override void Dispose()
public override async ValueTask DisposeAsync()
{
// Might have been already shutdown manually via WaitForConnectionShutdownAsync which nulls the _connectionStream.
if (_connectionStream != null)
{
ShutdownIgnoringErrorsAsync(_lastStreamId).GetAwaiter().GetResult();
await ShutdownIgnoringErrorsAsync(_lastStreamId);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,15 @@ public override void Dispose()

public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
using (Http2LoopbackConnection connection = await EstablishConnectionAsync().ConfigureAwait(false))
await using (Http2LoopbackConnection connection = await EstablishConnectionAsync().ConfigureAwait(false))
{
return await connection.HandleRequestAsync(statusCode, headers, content).ConfigureAwait(false);
}
}

public override async Task AcceptConnectionAsync(Func<GenericLoopbackConnection, Task> funcAsync)
{
using (Http2LoopbackConnection connection = await EstablishConnectionAsync().ConfigureAwait(false))
await using (Http2LoopbackConnection connection = await EstablishConnectionAsync().ConfigureAwait(false))
{
await funcAsync(connection).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ public Http3LoopbackConnection(QuicConnection connection)

public long MaxHeaderListSize { get; private set; } = -1;

public override void Dispose()
public override async ValueTask DisposeAsync()
{
// Close any remaining request streams (but NOT control streams, as these should not be closed while the connection is open)
foreach (Http3LoopbackStream stream in _openStreams.Values)
{
stream.Dispose();
await stream.DisposeAsync().ConfigureAwait(false);
}

foreach (QuicStream stream in _delayedStreams)
{
stream.Dispose();
await stream.DisposeAsync().ConfigureAwait(false);
}

// We don't dispose the connection currently, because this causes races when the server connection is closed before
Expand All @@ -79,8 +79,8 @@ public override void Dispose()
_connection.Dispose();

// Dispose control streams so that we release their handles too.
_inboundControlStream?.Dispose();
_outboundControlStream?.Dispose();
await _inboundControlStream?.DisposeAsync().ConfigureAwait(false);
await _outboundControlStream?.DisposeAsync().ConfigureAwait(false);
#endif
}

Expand All @@ -91,20 +91,20 @@ public async Task CloseAsync(long errorCode)

public async ValueTask<Http3LoopbackStream> OpenUnidirectionalStreamAsync()
{
return new Http3LoopbackStream(await _connection.OpenUnidirectionalStreamAsync());
return new Http3LoopbackStream(await _connection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional));
}

public async ValueTask<Http3LoopbackStream> OpenBidirectionalStreamAsync()
{
return new Http3LoopbackStream(await _connection.OpenBidirectionalStreamAsync());
return new Http3LoopbackStream(await _connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional));
}

public static int GetRequestId(QuicStream stream)
{
Debug.Assert(stream.CanRead && stream.CanWrite, "Stream must be a request stream.");

// TODO: QUIC streams can have IDs larger than int.MaxValue; update all our tests to use long rather than int.
return checked((int)stream.StreamId + 1);
return checked((int)stream.Id + 1);
}

public Http3LoopbackStream GetOpenRequest(int requestId = 0)
Expand All @@ -131,7 +131,7 @@ async Task EnsureControlStreamAcceptedInternalAsync()

while (true)
{
QuicStream quicStream = await _connection.AcceptStreamAsync().ConfigureAwait(false);
QuicStream quicStream = await _connection.AcceptInboundStreamAsync().ConfigureAwait(false);

if (!quicStream.CanWrite)
{
Expand Down Expand Up @@ -165,16 +165,16 @@ public async Task<Http3LoopbackStream> AcceptRequestStreamAsync()

if (!_delayedStreams.TryDequeue(out QuicStream quicStream))
{
quicStream = await _connection.AcceptStreamAsync().ConfigureAwait(false);
quicStream = await _connection.AcceptInboundStreamAsync().ConfigureAwait(false);
}

var stream = new Http3LoopbackStream(quicStream);

Assert.True(quicStream.CanWrite, "Expected writeable stream.");

_openStreams.Add(checked((int)quicStream.StreamId), stream);
_openStreams.Add(checked((int)quicStream.Id), stream);
_currentStream = stream;
_currentStreamId = quicStream.StreamId;
_currentStreamId = quicStream.Id;

return stream;
}
Expand Down Expand Up @@ -293,9 +293,9 @@ public async Task WaitForClientDisconnectAsync(bool refuseNewRequests = true)
break;
}

using (stream)
await using (stream)
{
await stream.AbortAndWaitForShutdownAsync(H3_REQUEST_REJECTED);
stream.Abort(H3_REQUEST_REJECTED);
}
}

Expand Down
18 changes: 10 additions & 8 deletions src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ public Http3LoopbackServer(Http3Options options = null)
{
var serverOptions = new QuicServerConnectionOptions()
{
MaxBidirectionalStreams = options.MaxBidirectionalStreams,
MaxUnidirectionalStreams = options.MaxUnidirectionalStreams,
DefaultStreamErrorCode = Http3LoopbackConnection.H3_REQUEST_CANCELLED,
DefaultCloseErrorCode = Http3LoopbackConnection.H3_NO_ERROR,
MaxInboundBidirectionalStreams = options.MaxInboundBidirectionalStreams,
MaxInboundUnidirectionalStreams = options.MaxInboundUnidirectionalStreams,
ServerAuthenticationOptions = new SslServerAuthenticationOptions
{
EnabledSslProtocols = options.SslProtocols,
Expand Down Expand Up @@ -80,14 +82,14 @@ public override async Task<GenericLoopbackConnection> EstablishGenericConnection

public override async Task AcceptConnectionAsync(Func<GenericLoopbackConnection, Task> funcAsync)
{
using Http3LoopbackConnection con = await EstablishHttp3ConnectionAsync().ConfigureAwait(false);
await using Http3LoopbackConnection con = await EstablishHttp3ConnectionAsync().ConfigureAwait(false);
await funcAsync(con).ConfigureAwait(false);
await con.ShutdownAsync();
}

public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
using var con = (Http3LoopbackConnection)await EstablishGenericConnectionAsync().ConfigureAwait(false);
await using Http3LoopbackConnection con = (Http3LoopbackConnection)await EstablishGenericConnectionAsync().ConfigureAwait(false);
return await con.HandleRequestAsync(statusCode, headers, content).ConfigureAwait(false);
}
}
Expand Down Expand Up @@ -136,16 +138,16 @@ private static Http3Options CreateOptions(GenericLoopbackOptions options)
}
public class Http3Options : GenericLoopbackOptions
{
public int MaxUnidirectionalStreams { get; set; }
public int MaxInboundUnidirectionalStreams { get; set; }

public int MaxBidirectionalStreams { get; set; }
public int MaxInboundBidirectionalStreams { get; set; }

public string Alpn { get; set; }

public Http3Options()
{
MaxUnidirectionalStreams = 10;
MaxBidirectionalStreams = 100;
MaxInboundUnidirectionalStreams = 10;
MaxInboundBidirectionalStreams = 100;
Alpn = SslApplicationProtocol.Http3.ToString();
}
}
Expand Down
21 changes: 7 additions & 14 deletions src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
namespace System.Net.Test.Common
{

internal sealed class Http3LoopbackStream : IDisposable
internal sealed class Http3LoopbackStream : IAsyncDisposable
{
private const int MaximumVarIntBytes = 8;
private const long VarIntMax = (1L << 62) - 1;
Expand All @@ -40,12 +40,9 @@ public Http3LoopbackStream(QuicStream stream)
_stream = stream;
}

public void Dispose()
{
_stream.Dispose();
}
public ValueTask DisposeAsync() => _stream.DisposeAsync();

public long StreamId => _stream.StreamId;
public long StreamId => _stream.Id;

public async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
Expand Down Expand Up @@ -285,9 +282,7 @@ public async Task SendResponseBodyAsync(byte[] content, bool isFinal = true)

if (isFinal)
{
_stream.Shutdown();
await _stream.ShutdownCompleted().ConfigureAwait(false);
Dispose();
_stream.CompleteWrites();
}
}

Expand Down Expand Up @@ -389,7 +384,7 @@ async Task WaitForWriteCancellation()
{
try
{
await _stream.WaitForWriteCompletionAsync();
await _stream.WritesClosed;
}
catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted && ex.ApplicationErrorCode == Http3LoopbackConnection.H3_REQUEST_CANCELLED)
{
Expand Down Expand Up @@ -424,11 +419,9 @@ private async Task DrainResponseData()
}
}

public async Task AbortAndWaitForShutdownAsync(long errorCode)
public void Abort(long errorCode)
{
_stream.AbortRead(errorCode);
_stream.AbortWrite(errorCode);
await _stream.ShutdownCompleted();
_stream.Abort(QuicAbortDirection.Both, errorCode);
}

public async Task<(long? frameType, byte[] payload)> ReadFrameAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,18 @@ public override async Task<GenericLoopbackConnection> EstablishGenericConnection
{
return connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false);
}
else
else
{
throw new Exception($"Invalid ClearTextVersion={_options.ClearTextVersion} specified");
}
}
catch
{
connection?.Dispose();
connection = null;
{
if (connection is not null)
{
await connection.DisposeAsync();
connection = null;
}
stream.Dispose();
throw;
}
Expand All @@ -132,15 +135,15 @@ public override async Task<GenericLoopbackConnection> EstablishGenericConnection

public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
using (GenericLoopbackConnection connection = await EstablishGenericConnectionAsync().ConfigureAwait(false))
await using (GenericLoopbackConnection connection = await EstablishGenericConnectionAsync().ConfigureAwait(false))
{
return await connection.HandleRequestAsync(statusCode, headers, content).ConfigureAwait(false);
}
}

public override async Task AcceptConnectionAsync(Func<GenericLoopbackConnection, Task> funcAsync)
{
using (GenericLoopbackConnection connection = await EstablishGenericConnectionAsync().ConfigureAwait(false))
await using (GenericLoopbackConnection connection = await EstablishGenericConnectionAsync().ConfigureAwait(false))
{
await funcAsync(connection).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ await LoopbackServer.CreateClientAndServerAsync(
Assert.Equal(0, requestData.GetHeaderValueCount("Authorization"));
// Establish a session connection
using var connection = await server.EstablishConnectionAsync();
await using LoopbackServer.Connection connection = await server.EstablishConnectionAsync();
requestData = await connection.ReadRequestDataAsync();
string authHeaderValue = requestData.GetSingleHeaderValue("Authorization");
Assert.Contains("NTLM", authHeaderValue);
Expand Down
11 changes: 8 additions & 3 deletions src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public async Task<Connection> EstablishConnectionAsync()

public async Task AcceptConnectionAsync(Func<Connection, Task> funcAsync)
{
using (Connection connection = await EstablishConnectionAsync().ConfigureAwait(false))
await using (Connection connection = await EstablishConnectionAsync().ConfigureAwait(false))
{
await funcAsync(connection).ConfigureAwait(false);
}
Expand Down Expand Up @@ -654,7 +654,7 @@ private async Task<byte[]> ReadLineBytesAsync()
return null;
}

public override void Dispose()
public override async ValueTask DisposeAsync()
{
try
{
Expand All @@ -666,7 +666,12 @@ public override void Dispose()
}
catch (Exception) { }

#if !NETSTANDARD2_0 && !NETFRAMEWORK
await _stream.DisposeAsync().ConfigureAwait(false);
#else
_stream.Dispose();
await Task.CompletedTask.ConfigureAwait(false);
#endif
_socket?.Dispose();
}

Expand Down Expand Up @@ -1076,7 +1081,7 @@ public override Task WaitForCloseAsync(CancellationToken cancellationToken)

public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
using (Connection connection = await EstablishConnectionAsync().ConfigureAwait(false))
await using (Connection connection = await EstablishConnectionAsync().ConfigureAwait(false))
{
return await connection.HandleRequestAsync(statusCode, headers, content).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
</PropertyGroup>

<ItemGroup>
<!-- Requires Private=true to calculate ReferenceCopyLocalPaths items. Also share System.Net.Quic which isn't part of aspnetcore's shared framework but which is needed by them. -->
<ProjectReference Include="@(AspNetCoreAppLibrary->'$(LibrariesProjectRoot)%(Identity)\src\%(Identity).csproj');
$(LibrariesProjectRoot)System.Net.Quic\src\System.Net.Quic.csproj"
<!-- Requires Private=true to calculate ReferenceCopyLocalPaths items. -->
<ProjectReference Include="@(AspNetCoreAppLibrary->'$(LibrariesProjectRoot)%(Identity)\src\%(Identity).csproj')"
Pack="true"
PrivateAssets="all"
Private="true"
Expand Down
1 change: 0 additions & 1 deletion src/libraries/NetCoreAppLibrary.props
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@
System.Xml.XPath.XDocument;
</NetCoreAppLibrary>
<NetCoreAppLibraryNoReference>
System.Net.Quic;
System.Private.CoreLib;
System.Private.DataContractSerialization;
System.Private.Uri;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ await LoopbackServer.CreateClientAndServerAsync(
},
async s =>
{
using (LoopbackServer.Connection connection = await s.EstablishConnectionAsync().ConfigureAwait(false))
await using (LoopbackServer.Connection connection = await s.EstablishConnectionAsync().ConfigureAwait(false))
{
SslStream sslStream = connection.Stream as SslStream;
Assert.NotNull(sslStream);
Expand Down Expand Up @@ -76,7 +76,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(
},
async s =>
{
using (Http2LoopbackConnection connection = await s.EstablishConnectionAsync().ConfigureAwait(false))
await using (Http2LoopbackConnection connection = await s.EstablishConnectionAsync().ConfigureAwait(false))
{
SslStream sslStream = connection.Stream as SslStream;
Assert.NotNull(sslStream);
Expand Down
Loading

0 comments on commit 060de1c

Please sign in to comment.