From a91f34658f0a30aead97a7b41f4f5bb13571a341 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 15 Nov 2019 11:04:45 -0800 Subject: [PATCH 01/27] factory --- .../Features/IConnectionTransportFeature.cs | 2 +- .../src/Features/IQuicCreateStreamFeature.cs | 13 - .../Features/IQuicStreamListenerFeature.cs | 12 - .../src/IConnectionFactory.cs | 5 +- .../src/MultiplexedConnectionContext.cs | 17 ++ .../src/StreamContext.cs | 33 +++ .../src/Internal/Http3/Http3Connection.cs | 36 +-- .../Kestrel/Core/test/KestrelServerTests.cs | 12 +- .../Kestrel/test/GeneratedCodeTests.cs | 22 +- ...tCore.Server.Kestrel.Transport.Quic.csproj | 9 +- .../src/WebHostBuilderMsQuicExtensions.cs | 2 +- .../src/Client/SocketConnectionFactory.cs | 3 +- .../Kestrel/samples/QuicSampleApp/Program.cs | 12 +- .../samples/QuicSampleClient/Program.cs | 4 +- ...MultiplexedConnection.FeatureCollection.cs | 37 +++ ...ransportMultiplexedConnection.Generated.cs | 248 ++++++++++++++++++ .../shared/TransportMultiplexedConnection.cs | 75 ++++++ .../TransportStream.FeatureCollection.cs | 44 ++++ .../shared/TransportStream.Generated.cs | 248 ++++++++++++++++++ src/Servers/Kestrel/shared/TransportStream.cs | 78 ++++++ .../Http3/Http3TestBase.cs | 135 ++++++---- .../tools/CodeGenerator/CodeGenerator.csproj | 2 +- .../Kestrel/tools/CodeGenerator/Program.cs | 52 ++-- .../TransportConnectionFeatureCollection.cs | 7 +- ...tMultiplexedConnectionFeatureCollection.cs | 34 +++ .../TransportStreamFeatureCollection.cs | 37 +++ 26 files changed, 1034 insertions(+), 145 deletions(-) delete mode 100644 src/Servers/Connections.Abstractions/src/Features/IQuicCreateStreamFeature.cs delete mode 100644 src/Servers/Connections.Abstractions/src/Features/IQuicStreamListenerFeature.cs create mode 100644 src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs create mode 100644 src/Servers/Connections.Abstractions/src/StreamContext.cs create mode 100644 src/Servers/Kestrel/shared/TransportMultiplexedConnection.FeatureCollection.cs create mode 100644 src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs create mode 100644 src/Servers/Kestrel/shared/TransportMultiplexedConnection.cs create mode 100644 src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs create mode 100644 src/Servers/Kestrel/shared/TransportStream.Generated.cs create mode 100644 src/Servers/Kestrel/shared/TransportStream.cs create mode 100644 src/Servers/Kestrel/tools/CodeGenerator/TransportMultiplexedConnectionFeatureCollection.cs create mode 100644 src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs diff --git a/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs index 0b218972d70c..deba5a8c1efd 100644 --- a/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs +++ b/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO.Pipelines; diff --git a/src/Servers/Connections.Abstractions/src/Features/IQuicCreateStreamFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IQuicCreateStreamFeature.cs deleted file mode 100644 index 1de25e43130d..000000000000 --- a/src/Servers/Connections.Abstractions/src/Features/IQuicCreateStreamFeature.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Connections.Abstractions.Features -{ - public interface IQuicCreateStreamFeature - { - ValueTask StartUnidirectionalStreamAsync(); - ValueTask StartBidirectionalStreamAsync(); - } -} diff --git a/src/Servers/Connections.Abstractions/src/Features/IQuicStreamListenerFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IQuicStreamListenerFeature.cs deleted file mode 100644 index e9f63aeb3667..000000000000 --- a/src/Servers/Connections.Abstractions/src/Features/IQuicStreamListenerFeature.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Connections.Features -{ - public interface IQuicStreamListenerFeature - { - ValueTask AcceptAsync(); - } -} diff --git a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs index 1b70c54eabef..a2ac35c67ca2 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs @@ -1,9 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { @@ -16,10 +18,11 @@ public interface IConnectionFactory /// Creates a new connection to an endpoint. /// /// The to connect to. + /// A feature collection to pass options when connecting. /// The token to monitor for cancellation requests. The default value is . /// /// A that represents the asynchronous connect, yielding the for the new connection when completed. /// - ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default); + ValueTask ConnectAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs new file mode 100644 index 000000000000..902854532b9e --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + public abstract class MultiplexedConnectionContext : ConnectionContext + { + public override IDuplexPipe Transport { get; set; } = null; + public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); + public abstract ValueTask ConnectAsync(IFeatureCollection features = null, bool unidirectional = false, CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/StreamContext.cs b/src/Servers/Connections.Abstractions/src/StreamContext.cs new file mode 100644 index 000000000000..9b3bf4ef7e8b --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/StreamContext.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Microsoft.AspNetCore.Connections +{ + public abstract class StreamContext : ConnectionContext + { + /// + /// Triggered when the client stream is closed. + /// + public virtual CancellationToken StreamClosed { get; set; } + + /// + /// Gets or sets a unique identifier to represent this stream in trace logs. + /// + public abstract long StreamId { get; set; } + + /// + /// Represents the direction + /// + public abstract Direction Direction { get; } + } + + public enum Direction + { + BidirectionalInbound, + BidirectionalOutbound, + UnidirectionalInbound, + UnidirectionalOutbound + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 9c1c4e888f07..8ce851ce5b37 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -9,8 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Abstractions.Features; -using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; @@ -31,9 +29,16 @@ internal class Http3Connection : IRequestProcessor private long _highestOpenedStreamId; // TODO lock to access private volatile bool _haveSentGoAway; private object _sync = new object(); + private MultiplexedConnectionContext _multiplexedContext; + //private volatile bool _haveSentGoAway; public Http3Connection(HttpConnectionContext context) { + if (!(context.ConnectionContext is MultiplexedConnectionContext)) + { + throw new ArgumentException("Must pass an MultiplexedConnectionContext into Http3Connection."); + } + _multiplexedContext = (MultiplexedConnectionContext)context.ConnectionContext; Context = context; DynamicTable = new DynamicTable(0); } @@ -55,8 +60,6 @@ internal long HighestStreamId public async Task ProcessRequestsAsync(IHttpApplication application) { - var streamListenerFeature = Context.ConnectionFeatures.Get(); - // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); var encoderTask = CreateEncoderStream(application); @@ -66,29 +69,29 @@ public async Task ProcessRequestsAsync(IHttpApplication appl { while (true) { - var connectionContext = await streamListenerFeature.AcceptAsync(); - if (connectionContext == null || _haveSentGoAway) + var streamContext = await _multiplexedContext.AcceptAsync(); + if (_multiplexedContext == null || _haveSentGoAway) { break; } var httpConnectionContext = new HttpConnectionContext { - ConnectionId = connectionContext.ConnectionId, - ConnectionContext = connectionContext, + ConnectionId = streamContext.ConnectionId, + ConnectionContext = streamContext, Protocols = Context.Protocols, ServiceContext = Context.ServiceContext, - ConnectionFeatures = connectionContext.Features, + ConnectionFeatures = streamContext.Features, MemoryPool = Context.MemoryPool, - Transport = connectionContext.Transport, + Transport = streamContext.Transport, TimeoutControl = Context.TimeoutControl, - LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint, - RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint + LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, + RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint }; - var streamFeature = httpConnectionContext.ConnectionFeatures.Get(); - if (!streamFeature.CanWrite) + + if (streamContext.Direction == Direction.UnidirectionalInbound) { // Unidirectional stream var stream = new Http3ControlStream(application, this, httpConnectionContext); @@ -97,8 +100,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl else { // Keep track of highest stream id seen for GOAWAY - var streamId = streamFeature.StreamId; - + var streamId = streamContext.StreamId; HighestStreamId = streamId; var http3Stream = new Http3Stream(application, this, httpConnectionContext); @@ -150,7 +152,7 @@ private async ValueTask CreateDecoderStream(IHttpApplication private async ValueTask CreateNewUnidirectionalStreamAsync(IHttpApplication application) { - var connectionContext = await Context.ConnectionFeatures.Get().StartUnidirectionalStreamAsync(); + var connectionContext = await _multiplexedContext.ConnectAsync(unidirectional: true); var httpConnectionContext = new HttpConnectionContext { //ConnectionId = "", TODO getting stream ID from stream that isn't started throws an exception. diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 56454bb7a41a..b51a262f4246 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -261,7 +261,7 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() var mockTransportFactory = new Mock(); mockTransportFactory .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) - .Returns((e, token) => + .Returns((Func>)((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) @@ -276,7 +276,7 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() .Setup(transport => transport.EndPoint).Returns(e); return new ValueTask(mockTransport.Object); - }); + })); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); @@ -318,7 +318,7 @@ public async Task StopAsyncCallsCompleteWithThrownException() var mockTransportFactory = new Mock(); mockTransportFactory .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) - .Returns((e, token) => + .Returns((Func>)((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) @@ -334,7 +334,7 @@ public async Task StopAsyncCallsCompleteWithThrownException() .Setup(transport => transport.EndPoint).Returns(e); return new ValueTask(mockTransport.Object); - }); + })); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); @@ -377,7 +377,7 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() var mockTransportFactory = new Mock(); mockTransportFactory .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) - .Returns((e, token) => + .Returns((Func>)((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) @@ -389,7 +389,7 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() .Setup(transport => transport.EndPoint).Returns(e); return new ValueTask(mockTransport.Object); - }); + })); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); diff --git a/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs b/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs index f3b0d3acda3e..4b1154229f58 100644 --- a/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs @@ -20,13 +20,17 @@ public void GeneratedCodeIsUpToDate() var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.Generated.cs"); var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpUtilities.Generated.cs"); var http2ConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "Http2Connection.Generated.cs"); - var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "TransportConnection.Generated.cs"); + var transportMultiplexedConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportConnectionBase.Generated.cs"); + var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportConnection.Generated.cs"); + var transportStreamGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportStream.Generated.cs"); var testHttpHeadersGeneratedPath = Path.GetTempFileName(); var testHttpProtocolGeneratedPath = Path.GetTempFileName(); var testHttpUtilitiesGeneratedPath = Path.GetTempFileName(); var testHttp2ConnectionGeneratedPath = Path.GetTempFileName(); + var testTransportMultiplexedConnectionGeneratedPath = Path.GetTempFileName(); var testTransportConnectionGeneratedPath = Path.GetTempFileName(); + var testTransportStreamGeneratedPath = Path.GetTempFileName(); try { @@ -34,21 +38,33 @@ public void GeneratedCodeIsUpToDate() var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath); var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath); var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath); + var currentTransportConnectionBaseGenerated = File.ReadAllText(transportMultiplexedConnectionGeneratedPath); var currentTransportConnectionGenerated = File.ReadAllText(transportConnectionGeneratedPath); + var currentTransportStreamGenerated = File.ReadAllText(transportStreamGeneratedPath); - CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, testHttpProtocolGeneratedPath, testHttpUtilitiesGeneratedPath, testTransportConnectionGeneratedPath, testHttp2ConnectionGeneratedPath); + CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, + testHttpProtocolGeneratedPath, + testHttpUtilitiesGeneratedPath, + testHttp2ConnectionGeneratedPath, + testTransportMultiplexedConnectionGeneratedPath, + testTransportConnectionGeneratedPath, + testTransportStreamGeneratedPath); var testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath); var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath); var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath); var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath); + var testTransportMultiplxedConnectionGenerated = File.ReadAllText(testTransportMultiplexedConnectionGeneratedPath); var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath); + var testTransportStreamGenerated = File.ReadAllText(testTransportStreamGeneratedPath); Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true); + Assert.Equal(currentTransportConnectionBaseGenerated, testTransportMultiplxedConnectionGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true); + Assert.Equal(currentTransportStreamGenerated, testTransportStreamGenerated, ignoreLineEndingDifferences: true); } finally { @@ -56,7 +72,9 @@ public void GeneratedCodeIsUpToDate() File.Delete(testHttpProtocolGeneratedPath); File.Delete(testHttpUtilitiesGeneratedPath); File.Delete(testHttp2ConnectionGeneratedPath); + File.Delete(testTransportMultiplexedConnectionGeneratedPath); File.Delete(testTransportConnectionGeneratedPath); + File.Delete(testTransportStreamGeneratedPath); } } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj index 19b9c387617f..7d62efe33867 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj +++ b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj @@ -17,9 +17,12 @@ - - - + + + + + + diff --git a/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs index 25fb0b581a0e..57ac9024e3a5 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs @@ -14,7 +14,7 @@ public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder) { return hostBuilder.ConfigureServices(services => { - services.AddSingleton(); + services.AddSingleton(); }); } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs index 1a3f0ed601be..4f3449c0370e 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -39,7 +40,7 @@ public SocketConnectionFactory(IOptions options, ILogger _trace = new SocketsTrace(logger); } - public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) + public async ValueTask ConnectAsync(EndPoint endpoint, IFeatureCollection collection = null, CancellationToken cancellationToken = default) { var ipEndPoint = endpoint as IPEndPoint; diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index e2b6b7b5df36..2190f0cb000c 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -5,11 +5,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Logging; -using Microsoft.AspNetCore.Server.Kestrel.Core; namespace QuicSampleApp { @@ -50,17 +49,16 @@ public static void Main(string[] args) { return async connection => { - var streamFeature = connection.Features.Get(); - if (streamFeature != null) + if (connection is MultiplexedConnectionContext) { while (true) { - var connectionContext = await streamFeature.AcceptAsync(); - if (connectionContext == null) + var streamContext = await ((MultiplexedConnectionContext)connection).AcceptAsync(); + if (streamContext == null) { return; } - _ = next(connectionContext); + _ = next(streamContext); } } else diff --git a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs index 3ad16e0dd652..f6bc0bfab660 100644 --- a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic; -using Microsoft.AspNetCore.Connections.Abstractions.Features; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; @@ -55,8 +54,7 @@ public async Task RunAsync() { Console.WriteLine("Starting"); var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555)); - var createStreamFeature = connectionContext.Features.Get(); - var streamContext = await createStreamFeature.StartBidirectionalStreamAsync(); + var streamContext = await (connectionContext as MultiplexedConnectionContext).ConnectAsync(); Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, args) => { diff --git a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.FeatureCollection.cs b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.FeatureCollection.cs new file mode 100644 index 000000000000..fae4129d25c5 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.FeatureCollection.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using Microsoft.AspNetCore.Connections.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal partial class TransportMultiplexedConnection : IConnectionIdFeature, + IConnectionItemsFeature, + IMemoryPoolFeature, + IConnectionLifetimeFeature + { + // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, + // then the list of `features` in the generated code project MUST also be updated. + // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs + + MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool; + + IDictionary IConnectionItemsFeature.Items + { + get => Items; + set => Items = value; + } + + CancellationToken IConnectionLifetimeFeature.ConnectionClosed + { + get => ConnectionClosed; + set => ConnectionClosed = value; + } + + void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); + } +} diff --git a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs new file mode 100644 index 000000000000..41fec23fc846 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs @@ -0,0 +1,248 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; + +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal partial class TransportMultiplexedConnection : IFeatureCollection + { + private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); + private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); + private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); + private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); + private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); + + private object _currentIConnectionIdFeature; + private object _currentIConnectionTransportFeature; + private object _currentIConnectionItemsFeature; + private object _currentIMemoryPoolFeature; + private object _currentIConnectionLifetimeFeature; + + private int _featureRevision; + + private List> MaybeExtra; + + private void FastReset() + { + _currentIConnectionIdFeature = this; + _currentIConnectionTransportFeature = this; + _currentIConnectionItemsFeature = this; + _currentIMemoryPoolFeature = this; + _currentIConnectionLifetimeFeature = this; + + } + + // Internal for testing + internal void ResetFeatureCollection() + { + FastReset(); + MaybeExtra?.Clear(); + _featureRevision++; + } + + private object ExtraFeatureGet(Type key) + { + if (MaybeExtra == null) + { + return null; + } + for (var i = 0; i < MaybeExtra.Count; i++) + { + var kv = MaybeExtra[i]; + if (kv.Key == key) + { + return kv.Value; + } + } + return null; + } + + private void ExtraFeatureSet(Type key, object value) + { + if (MaybeExtra == null) + { + MaybeExtra = new List>(2); + } + + for (var i = 0; i < MaybeExtra.Count; i++) + { + if (MaybeExtra[i].Key == key) + { + MaybeExtra[i] = new KeyValuePair(key, value); + return; + } + } + MaybeExtra.Add(new KeyValuePair(key, value)); + } + + bool IFeatureCollection.IsReadOnly => false; + + int IFeatureCollection.Revision => _featureRevision; + + object IFeatureCollection.this[Type key] + { + get + { + object feature = null; + if (key == IConnectionIdFeatureType) + { + feature = _currentIConnectionIdFeature; + } + else if (key == IConnectionTransportFeatureType) + { + feature = _currentIConnectionTransportFeature; + } + else if (key == IConnectionItemsFeatureType) + { + feature = _currentIConnectionItemsFeature; + } + else if (key == IMemoryPoolFeatureType) + { + feature = _currentIMemoryPoolFeature; + } + else if (key == IConnectionLifetimeFeatureType) + { + feature = _currentIConnectionLifetimeFeature; + } + else if (MaybeExtra != null) + { + feature = ExtraFeatureGet(key); + } + + return feature; + } + + set + { + _featureRevision++; + + if (key == IConnectionIdFeatureType) + { + _currentIConnectionIdFeature = value; + } + else if (key == IConnectionTransportFeatureType) + { + _currentIConnectionTransportFeature = value; + } + else if (key == IConnectionItemsFeatureType) + { + _currentIConnectionItemsFeature = value; + } + else if (key == IMemoryPoolFeatureType) + { + _currentIMemoryPoolFeature = value; + } + else if (key == IConnectionLifetimeFeatureType) + { + _currentIConnectionLifetimeFeature = value; + } + else + { + ExtraFeatureSet(key, value); + } + } + } + + TFeature IFeatureCollection.Get() + { + TFeature feature = default; + if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + feature = (TFeature)_currentIConnectionIdFeature; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + feature = (TFeature)_currentIConnectionTransportFeature; + } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + feature = (TFeature)_currentIConnectionItemsFeature; + } + else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) + { + feature = (TFeature)_currentIMemoryPoolFeature; + } + else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) + { + feature = (TFeature)_currentIConnectionLifetimeFeature; + } + else if (MaybeExtra != null) + { + feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); + } + + return feature; + } + + void IFeatureCollection.Set(TFeature feature) + { + _featureRevision++; + if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + _currentIConnectionIdFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + _currentIConnectionTransportFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + _currentIConnectionItemsFeature = feature; + } + else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) + { + _currentIMemoryPoolFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) + { + _currentIConnectionLifetimeFeature = feature; + } + else + { + ExtraFeatureSet(typeof(TFeature), feature); + } + } + + private IEnumerable> FastEnumerable() + { + if (_currentIConnectionIdFeature != null) + { + yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); + } + if (_currentIConnectionTransportFeature != null) + { + yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); + } + if (_currentIConnectionItemsFeature != null) + { + yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); + } + if (_currentIMemoryPoolFeature != null) + { + yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); + } + if (_currentIConnectionLifetimeFeature != null) + { + yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); + } + + if (MaybeExtra != null) + { + foreach (var item in MaybeExtra) + { + yield return item; + } + } + } + + IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); + } +} diff --git a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.cs b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.cs new file mode 100644 index 000000000000..7dea0569b9f7 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net; +using System.Threading; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal abstract partial class TransportMultiplexedConnection : MultiplexedConnectionContext + { + private IDictionary _items; + private string _connectionId; + + public TransportMultiplexedConnection() + { + FastReset(); + } + + public override EndPoint LocalEndPoint { get; set; } + public override EndPoint RemoteEndPoint { get; set; } + + public override string ConnectionId + { + get + { + if (_connectionId == null) + { + _connectionId = CorrelationIdGenerator.GetNextId(); + } + + return _connectionId; + } + set + { + _connectionId = value; + } + } + + public override IFeatureCollection Features => this; + + public virtual MemoryPool MemoryPool { get; } + + public IDuplexPipe Application { get; set; } + + public override IDictionary Items + { + get + { + // Lazily allocate connection metadata + return _items ?? (_items = new ConnectionItems()); + } + set + { + _items = value; + } + } + + public override CancellationToken ConnectionClosed { get; set; } + + // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause + // any TransportConnection that does not override Abort or calls base.Abort + // to stack overflow when IConnectionLifetimeFeature.Abort() is called. + // That said, all derived types should override this method should override + // this implementation of Abort because canceling pending output reads is not + // sufficient to abort the connection if there is backpressure. + public override void Abort(ConnectionAbortedException abortReason) + { + Application.Input.CancelPendingRead(); + } + } +} diff --git a/src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs b/src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs new file mode 100644 index 000000000000..f97e87be73e3 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using Microsoft.AspNetCore.Connections.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal partial class TransportStream : IConnectionIdFeature, + IConnectionTransportFeature, + IConnectionItemsFeature, + IMemoryPoolFeature, + IConnectionLifetimeFeature + { + // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, + // then the list of `features` in the generated code project MUST also be updated. + // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs + + MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool; + + IDuplexPipe IConnectionTransportFeature.Transport + { + get => Transport; + set => Transport = value; + } + + IDictionary IConnectionItemsFeature.Items + { + get => Items; + set => Items = value; + } + + CancellationToken IConnectionLifetimeFeature.ConnectionClosed + { + get => ConnectionClosed; + set => ConnectionClosed = value; + } + + void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); + } +} diff --git a/src/Servers/Kestrel/shared/TransportStream.Generated.cs b/src/Servers/Kestrel/shared/TransportStream.Generated.cs new file mode 100644 index 000000000000..1a81910a7f78 --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportStream.Generated.cs @@ -0,0 +1,248 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; + +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal partial class TransportStream : IFeatureCollection + { + private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); + private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); + private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); + private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); + private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); + + private object _currentIConnectionIdFeature; + private object _currentIConnectionTransportFeature; + private object _currentIConnectionItemsFeature; + private object _currentIMemoryPoolFeature; + private object _currentIConnectionLifetimeFeature; + + private int _featureRevision; + + private List> MaybeExtra; + + private void FastReset() + { + _currentIConnectionIdFeature = this; + _currentIConnectionTransportFeature = this; + _currentIConnectionItemsFeature = this; + _currentIMemoryPoolFeature = this; + _currentIConnectionLifetimeFeature = this; + + } + + // Internal for testing + internal void ResetFeatureCollection() + { + FastReset(); + MaybeExtra?.Clear(); + _featureRevision++; + } + + private object ExtraFeatureGet(Type key) + { + if (MaybeExtra == null) + { + return null; + } + for (var i = 0; i < MaybeExtra.Count; i++) + { + var kv = MaybeExtra[i]; + if (kv.Key == key) + { + return kv.Value; + } + } + return null; + } + + private void ExtraFeatureSet(Type key, object value) + { + if (MaybeExtra == null) + { + MaybeExtra = new List>(2); + } + + for (var i = 0; i < MaybeExtra.Count; i++) + { + if (MaybeExtra[i].Key == key) + { + MaybeExtra[i] = new KeyValuePair(key, value); + return; + } + } + MaybeExtra.Add(new KeyValuePair(key, value)); + } + + bool IFeatureCollection.IsReadOnly => false; + + int IFeatureCollection.Revision => _featureRevision; + + object IFeatureCollection.this[Type key] + { + get + { + object feature = null; + if (key == IConnectionIdFeatureType) + { + feature = _currentIConnectionIdFeature; + } + else if (key == IConnectionTransportFeatureType) + { + feature = _currentIConnectionTransportFeature; + } + else if (key == IConnectionItemsFeatureType) + { + feature = _currentIConnectionItemsFeature; + } + else if (key == IMemoryPoolFeatureType) + { + feature = _currentIMemoryPoolFeature; + } + else if (key == IConnectionLifetimeFeatureType) + { + feature = _currentIConnectionLifetimeFeature; + } + else if (MaybeExtra != null) + { + feature = ExtraFeatureGet(key); + } + + return feature; + } + + set + { + _featureRevision++; + + if (key == IConnectionIdFeatureType) + { + _currentIConnectionIdFeature = value; + } + else if (key == IConnectionTransportFeatureType) + { + _currentIConnectionTransportFeature = value; + } + else if (key == IConnectionItemsFeatureType) + { + _currentIConnectionItemsFeature = value; + } + else if (key == IMemoryPoolFeatureType) + { + _currentIMemoryPoolFeature = value; + } + else if (key == IConnectionLifetimeFeatureType) + { + _currentIConnectionLifetimeFeature = value; + } + else + { + ExtraFeatureSet(key, value); + } + } + } + + TFeature IFeatureCollection.Get() + { + TFeature feature = default; + if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + feature = (TFeature)_currentIConnectionIdFeature; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + feature = (TFeature)_currentIConnectionTransportFeature; + } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + feature = (TFeature)_currentIConnectionItemsFeature; + } + else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) + { + feature = (TFeature)_currentIMemoryPoolFeature; + } + else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) + { + feature = (TFeature)_currentIConnectionLifetimeFeature; + } + else if (MaybeExtra != null) + { + feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); + } + + return feature; + } + + void IFeatureCollection.Set(TFeature feature) + { + _featureRevision++; + if (typeof(TFeature) == typeof(IConnectionIdFeature)) + { + _currentIConnectionIdFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + { + _currentIConnectionTransportFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) + { + _currentIConnectionItemsFeature = feature; + } + else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) + { + _currentIMemoryPoolFeature = feature; + } + else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) + { + _currentIConnectionLifetimeFeature = feature; + } + else + { + ExtraFeatureSet(typeof(TFeature), feature); + } + } + + private IEnumerable> FastEnumerable() + { + if (_currentIConnectionIdFeature != null) + { + yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); + } + if (_currentIConnectionTransportFeature != null) + { + yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); + } + if (_currentIConnectionItemsFeature != null) + { + yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); + } + if (_currentIMemoryPoolFeature != null) + { + yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); + } + if (_currentIConnectionLifetimeFeature != null) + { + yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); + } + + if (MaybeExtra != null) + { + foreach (var item in MaybeExtra) + { + yield return item; + } + } + } + + IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); + } +} diff --git a/src/Servers/Kestrel/shared/TransportStream.cs b/src/Servers/Kestrel/shared/TransportStream.cs new file mode 100644 index 000000000000..be77c0a96d8a --- /dev/null +++ b/src/Servers/Kestrel/shared/TransportStream.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net; +using System.Threading; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + internal abstract partial class TransportStream : StreamContext + { + private IDictionary _items; + private string _connectionId; + + public TransportStream() + { + FastReset(); + } + + public override EndPoint LocalEndPoint { get; set; } + public override EndPoint RemoteEndPoint { get; set; } + + public override string ConnectionId + { + get + { + if (_connectionId == null) + { + _connectionId = CorrelationIdGenerator.GetNextId(); + } + + return _connectionId; + } + set + { + _connectionId = value; + } + } + + public override IFeatureCollection Features => this; + + public virtual MemoryPool MemoryPool { get; } + + public override IDuplexPipe Transport { get; set; } + + public IDuplexPipe Application { get; set; } + + public override IDictionary Items + { + get + { + // Lazily allocate connection metadata + return _items ?? (_items = new ConnectionItems()); + } + set + { + _items = value; + } + } + + public override CancellationToken ConnectionClosed { get; set; } + + // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause + // any TransportConnection that does not override Abort or calls base.Abort + // to stack overflow when IConnectionLifetimeFeature.Abort() is called. + // That said, all derived types should override this method should override + // this implementation of Abort because canceling pending output reads is not + // sufficient to abort the connection if there is backpressure. + public override void Abort(ConnectionAbortedException abortReason) + { + Application.Input.CancelPendingRead(); + } + } +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index dd7133e0d43e..b9793e339640 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -6,15 +6,13 @@ using System.Net.Http; using System.Net.Http.QPack; using System.Reflection; +using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Abstractions.Features; -using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -26,24 +24,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, IQuicCreateStreamFeature, IQuicStreamListenerFeature + public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable { internal TestServiceContext _serviceContext; internal Http3Connection _connection; internal readonly TimeoutControl _timeoutControl; internal readonly Mock _mockKestrelTrace = new Mock(); - protected readonly Mock _mockConnectionContext = new Mock(); internal readonly Mock _mockTimeoutHandler = new Mock(); internal readonly Mock _mockTimeoutControl; internal readonly MemoryPool _memoryPool = SlabMemoryPoolFactory.Create(); protected Task _connectionTask; protected readonly RequestDelegate _echoApplication; - - private readonly Channel _acceptConnectionQueue = Channel.CreateUnbounded(new UnboundedChannelOptions - { - SingleReader = true, - SingleWriter = true - }); + private TestMultiplexedConnectionContext _multiplexedContext; public Http3TestBase() { @@ -100,12 +92,12 @@ protected void CreateConnection() var limits = _serviceContext.ServerOptions.Limits; var features = new FeatureCollection(); - features.Set(this); - features.Set(this); + + _multiplexedContext = new TestMultiplexedConnectionContext(this); var httpConnectionContext = new HttpConnectionContext { - ConnectionContext = _mockConnectionContext.Object, + ConnectionContext = _multiplexedContext, ConnectionFeatures = features, ServiceContext = _serviceContext, MemoryPool = _memoryPool, @@ -155,30 +147,10 @@ private static long GetOutputResponseBufferSize(ServiceContext serviceContext) return bufferSize ?? 0; } - public ValueTask StartUnidirectionalStreamAsync() - { - var stream = new Http3ControlStream(this, _connection); - // TODO put these somewhere to be read. - return new ValueTask(stream.ConnectionContext); - } - - public async ValueTask AcceptAsync() - { - while (await _acceptConnectionQueue.Reader.WaitToReadAsync()) - { - while (_acceptConnectionQueue.Reader.TryRead(out var connection)) - { - return connection; - } - } - - return null; - } - internal async ValueTask CreateControlStream(int id) { var stream = new Http3ControlStream(this, _connection); - _acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext); + _multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext); await stream.WriteStreamIdAsync(id); return stream; } @@ -186,15 +158,15 @@ internal async ValueTask CreateControlStream(int id) internal ValueTask CreateRequestStream() { var stream = new Http3RequestStream(this, _connection); - _acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext); + _multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext); return new ValueTask(stream); } - public ValueTask StartBidirectionalStreamAsync() + public ValueTask StartBidirectionalStreamAsync() { var stream = new Http3RequestStream(this, _connection); // TODO put these somewhere to be read. - return new ValueTask(stream.ConnectionContext); + return new ValueTask(stream.StreamContext); } internal class Http3StreamBase @@ -216,9 +188,9 @@ protected static async Task FlushAsync(PipeWriter writableBuffer) } } - internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler, IQuicStreamFeature + internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler { - internal ConnectionContext ConnectionContext { get; } + internal StreamContext StreamContext { get; } public bool CanRead => true; public bool CanWrite => true; @@ -239,10 +211,9 @@ public Http3RequestStream(Http3TestBase testBase, Http3Connection connection) var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - - ConnectionContext = new DefaultConnectionContext(); - ConnectionContext.Transport = _pair.Transport; - ConnectionContext.Features.Set(this); + + StreamContext = new TestStreamContext(Direction.BidirectionalInbound); + StreamContext.Transport = _pair.Transport; } public async Task SendHeadersAsync(IEnumerable> headers) @@ -362,28 +333,26 @@ public Http3FrameWithPayload() : base() } - internal class Http3ControlStream : Http3StreamBase, IQuicStreamFeature + internal class Http3ControlStream : Http3StreamBase { - internal ConnectionContext ConnectionContext { get; } + internal StreamContext StreamContext { get; } public bool CanRead => true; public bool CanWrite => false; - // TODO public long StreamId => 0; - public Http3ControlStream(Http3TestBase testBase, Http3Connection connection) + public Http3ControlStream(Http3TestBase testBase) { _testBase = testBase; - _connection = connection; var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - ConnectionContext = new DefaultConnectionContext(); - ConnectionContext.Transport = _pair.Transport; - ConnectionContext.Features.Set(this); + StreamContext = new TestStreamContext(Direction.UnidirectionalInbound); + + StreamContext.Transport = _pair.Transport; } public async Task WriteStreamIdAsync(int id) @@ -402,5 +371,67 @@ void WriteSpan(PipeWriter pw) await FlushAsync(writableBuffer); } } + + private class TestMultiplexedConnectionContext : MultiplexedConnectionContext + { + public readonly Channel AcceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions + { + SingleReader = true, + SingleWriter = true + }); + + private readonly Http3TestBase _testBase; + + public TestMultiplexedConnectionContext(Http3TestBase testBase) + { + _testBase = testBase; + } + + public override string ConnectionId { get; set; } + + public override IFeatureCollection Features { get; } + + public override IDictionary Items { get; set; } + + public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + while (await AcceptQueue.Reader.WaitToReadAsync()) + { + while (AcceptQueue.Reader.TryRead(out var connection)) + { + return connection; + } + } + + return null; + } + + public override ValueTask ConnectAsync(IFeatureCollection features = null, bool unidirectional = false, CancellationToken cancellationToken = default) + { + var stream = new Http3ControlStream(_testBase); + // TODO put these somewhere to be read. + return new ValueTask(stream.StreamContext); + } + } + + private class TestStreamContext : StreamContext + { + public TestStreamContext(Direction direction) + { + Direction = direction; + } + + public override long StreamId { get; set; } + + public override Direction Direction { get; } + + public override string ConnectionId { get; set; } + + public override IFeatureCollection Features { get; } + + public override IDictionary Items { get; set; } + + public override IDuplexPipe Transport { get; set; } + } } } diff --git a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj index 7337ccfc0cb5..ac8debc0a3ae 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj +++ b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj @@ -18,7 +18,7 @@ $(MSBuildThisFileDirectory)..\..\ - Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs shared/TransportConnection.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs + Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs shared/TransportMultiplexedConnection.Generated.cs shared/TransportConnection.Generated.cs shared/TransportStream.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs diff --git a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs index c319d9bfb346..fc3d9bc818b3 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs @@ -27,16 +27,26 @@ public static int Main(string[] args) } else if (args.Length < 4) { - Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs"); + Console.Error.WriteLine("Missing path to TransportMultiplexedConnection.Generated.cs"); return 1; } else if (args.Length < 5) + { + Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs"); + return 1; + } + else if (args.Length < 6) { Console.Error.WriteLine("Missing path to Http2Connection.Generated.cs"); return 1; } + else if (args.Length < 7) + { + Console.Error.WriteLine("Missing path to TransportStream.Generated.cs"); + return 1; + } - Run(args[0], args[1], args[2], args[3], args[4]); + Run(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); return 0; } @@ -45,37 +55,33 @@ public static void Run( string knownHeadersPath, string httpProtocolFeatureCollectionPath, string httpUtilitiesPath, + string http2ConnectionPath, + string transportMultiplexedConnectionFeatureCollectionPath, string transportConnectionFeatureCollectionPath, - string http2ConnectionPath) + string transportStreamFeatureCollectionPath) { var knownHeadersContent = KnownHeaders.GeneratedFile(); var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile(); var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile(); + var transportMultiplexedConnectionFeatureCollectionContent = TransportMultiplexedConnectionFeatureCollection.GenerateFile(); var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile(); var http2ConnectionContent = Http2Connection.GenerateFile(); + var transportStreamFeatureCollectionContent = TransportStreamFeatureCollection.GenerateFile(); - var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : ""; - if (!string.Equals(knownHeadersContent, existingKnownHeaders)) - { - File.WriteAllText(knownHeadersPath, knownHeadersContent); - } - - var existingHttpProtocolFeatureCollection = File.Exists(httpProtocolFeatureCollectionPath) ? File.ReadAllText(httpProtocolFeatureCollectionPath) : ""; - if (!string.Equals(httpProtocolFeatureCollectionContent, existingHttpProtocolFeatureCollection)) - { - File.WriteAllText(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); - } - - var existingHttpUtilities = File.Exists(httpUtilitiesPath) ? File.ReadAllText(httpUtilitiesPath) : ""; - if (!string.Equals(httpUtilitiesContent, existingHttpUtilities)) - { - File.WriteAllText(httpUtilitiesPath, httpUtilitiesContent); - } + UpdateFile(knownHeadersPath, knownHeadersContent); + UpdateFile(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); + UpdateFile(httpUtilitiesPath, httpUtilitiesContent); + UpdateFile(transportMultiplexedConnectionFeatureCollectionPath, transportMultiplexedConnectionFeatureCollectionContent); + UpdateFile(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); + UpdateFile(transportStreamFeatureCollectionPath, transportStreamFeatureCollectionContent); + } - var existingTransportConnectionFeatureCollection = File.Exists(transportConnectionFeatureCollectionPath) ? File.ReadAllText(transportConnectionFeatureCollectionPath) : ""; - if (!string.Equals(transportConnectionFeatureCollectionContent, existingTransportConnectionFeatureCollection)) + public static void UpdateFile(string path, string content) + { + var existingContent = File.Exists(path) ? File.ReadAllText(path) : ""; + if (!string.Equals(content, existingContent)) { - File.WriteAllText(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); + File.WriteAllText(path, content); } var existingHttp2Connection = File.Exists(http2ConnectionPath) ? File.ReadAllText(http2ConnectionPath) : ""; diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index fdc759e7f31f..4da91d15374f 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -1,5 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace CodeGenerator { diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportMultiplexedConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportMultiplexedConnectionFeatureCollection.cs new file mode 100644 index 000000000000..8c0ad9d3ff99 --- /dev/null +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportMultiplexedConnectionFeatureCollection.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace CodeGenerator +{ + public class TransportMultiplexedConnectionFeatureCollection + { + public static string GenerateFile() + { + // NOTE: This list MUST always match the set of feature interfaces implemented by TransportConnectionBase. + // See also: shared/TransportConnectionBase.FeatureCollection.cs + var features = new[] + { + "IConnectionIdFeature", + "IConnectionTransportFeature", + "IConnectionItemsFeature", + "IMemoryPoolFeature", + "IConnectionLifetimeFeature" + }; + + var usings = $@" +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features;"; + + return FeatureCollectionGenerator.GenerateFile( + namespaceName: "Microsoft.AspNetCore.Connections", + className: "TransportMultiplexedConnection", + allFeatures: features, + implementedFeatures: features, + extraUsings: usings, + fallbackFeatures: null); + } + } +} diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs new file mode 100644 index 000000000000..a6829209f017 --- /dev/null +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodeGenerator +{ + public class TransportStreamFeatureCollection + { + public static string GenerateFile() + { + // NOTE: This list MUST always match the set of feature interfaces implemented by TransportStream. + // See also: shared/TransportStream.FeatureCollection.cs + var features = new[] + { + "IConnectionIdFeature", + "IConnectionTransportFeature", + "IConnectionItemsFeature", + "IMemoryPoolFeature", + "IConnectionLifetimeFeature" + }; + + var usings = $@" +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features;"; + + return FeatureCollectionGenerator.GenerateFile( + namespaceName: "Microsoft.AspNetCore.Connections", + className: "TransportStream", + allFeatures: features, + implementedFeatures: features, + extraUsings: usings, + fallbackFeatures: null); + } + } +} From 578f459f2bd28bbb819c537aceac7850a4ca5cca Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 18 Nov 2019 16:34:29 -0800 Subject: [PATCH 02/27] Idk why this was changed --- src/Servers/Kestrel/Core/test/KestrelServerTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index b51a262f4246..56454bb7a41a 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -261,7 +261,7 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() var mockTransportFactory = new Mock(); mockTransportFactory .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) - .Returns((Func>)((e, token) => + .Returns((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) @@ -276,7 +276,7 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() .Setup(transport => transport.EndPoint).Returns(e); return new ValueTask(mockTransport.Object); - })); + }); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); @@ -318,7 +318,7 @@ public async Task StopAsyncCallsCompleteWithThrownException() var mockTransportFactory = new Mock(); mockTransportFactory .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) - .Returns((Func>)((e, token) => + .Returns((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) @@ -334,7 +334,7 @@ public async Task StopAsyncCallsCompleteWithThrownException() .Setup(transport => transport.EndPoint).Returns(e); return new ValueTask(mockTransport.Object); - })); + }); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); @@ -377,7 +377,7 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() var mockTransportFactory = new Mock(); mockTransportFactory .Setup(transportFactory => transportFactory.BindAsync(It.IsAny(), It.IsAny())) - .Returns((Func>)((e, token) => + .Returns((e, token) => { mockTransport .Setup(transport => transport.AcceptAsync(It.IsAny())) @@ -389,7 +389,7 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() .Setup(transport => transport.EndPoint).Returns(e); return new ValueTask(mockTransport.Object); - })); + }); var mockLoggerFactory = new Mock(); var mockLogger = new Mock(); From 1a53477348764d34282019faf38afc1c04ca9325 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 11 Dec 2019 10:55:19 -0800 Subject: [PATCH 03/27] Start of what it would look like to split the world --- .../src/IMulitplexedConnectionListener.cs | 35 ++++++++++ .../src/IMultiplexedConnectionBuilder.cs | 31 +++++++++ .../src/IMultiplexedConnectionFactory.cs | 28 ++++++++ .../IMultiplexedConnectionListenerFactory.cs | 16 ++++- .../src/MultiplexedConnectionBuilder.cs | 43 +++++++++++++ .../src/MultiplexedConnectionContext.cs | 64 ++++++++++++++++++- .../src/MultiplexedConnectionDelegate.cs | 14 ++++ .../src/Internal/Http3/Http3Connection.cs | 6 +- .../src/Internal/HttpConnectionContext.cs | 3 +- src/Servers/Kestrel/Core/src/KestrelServer.cs | 2 + src/Servers/Kestrel/Core/src/ListenOptions.cs | 25 +++++++- .../HttpMultiplexedConnectionMiddleware.cs | 46 +++++++++++++ .../Kestrel/samples/QuicSampleApp/Program.cs | 19 ++---- .../samples/QuicSampleClient/Program.cs | 8 +-- .../Http3/Http3TestBase.cs | 3 +- .../Kestrel/tools/CodeGenerator/Program.cs | 7 +- 16 files changed, 318 insertions(+), 32 deletions(-) create mode 100644 src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs create mode 100644 src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs create mode 100644 src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs create mode 100644 src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs create mode 100644 src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs create mode 100644 src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs diff --git a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs new file mode 100644 index 000000000000..d83133f31728 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// Defines an interface that represents a listener bound to a specific . + /// + public interface IMultiplexedConnectionListener : IAsyncDisposable + { + /// + /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. + /// + EndPoint EndPoint { get; } + + /// + /// Begins an asynchronous operation to accept an incoming connection. + /// + /// The token to monitor for cancellation requests. + /// A that completes when a connection is accepted, yielding the representing the connection. + ValueTask AcceptAsync(CancellationToken cancellationToken = default); + + /// + /// Stops listening for incoming connections. + /// + /// The token to monitor for cancellation requests. + /// A that represents the un-bind operation. + ValueTask UnbindAsync(CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs new file mode 100644 index 000000000000..10eef0042296 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// Defines an interface that provides the mechanisms to configure a connection pipeline. + /// + public interface IMultiplexedConnectionBuilder + { + /// + /// Gets the that provides access to the application's service container. + /// + IServiceProvider ApplicationServices { get; } + + /// + /// Adds a middleware delegate to the application's connection pipeline. + /// + /// The middleware delegate. + /// The . + IMultiplexedConnectionBuilder UseMultiplexed(Func middleware); + + /// + /// Builds the delegate used by this application to process connections. + /// + /// The connection handling delegate. + MultiplexedConnectionDelegate BuildMultiplexed(); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs new file mode 100644 index 000000000000..83c2bc4f9d11 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// A factory abstraction for creating connections to an endpoint. + /// + public interface IMultiplexedConnectionFactory + { + /// + /// Creates a new connection to an endpoint. + /// + /// The to connect to. + /// A feature collection to pass options when connecting. + /// The token to monitor for cancellation requests. The default value is . + /// + /// A that represents the asynchronous connect, yielding the for the new connection when completed. + /// + ValueTask ConnectAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs index 65727b7ad831..7da3636db674 100644 --- a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs @@ -1,9 +1,23 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Net; +using System.Threading; +using System.Threading.Tasks; + namespace Microsoft.AspNetCore.Connections { - public interface IMultiplexedConnectionListenerFactory : IConnectionListenerFactory + /// + /// Defines an interface that provides the mechanisms for binding to various types of s. + /// + public interface IMultiplexedConnectionListenerFactory { + /// + /// Creates an bound to the specified . + /// + /// The to bind to. + /// The token to monitor for cancellation requests. + /// A that completes when the listener has been bound, yielding a representing the new listener. + ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs new file mode 100644 index 000000000000..202f29df5eb5 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + public class MultiplexedConnectionBuilder : IMultiplexedConnectionBuilder + { + private readonly IList> _components = new List>(); + + public IServiceProvider ApplicationServices { get; } + + public MultiplexedConnectionBuilder(IServiceProvider applicationServices) + { + ApplicationServices = applicationServices; + } + + public IMultiplexedConnectionBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public MultiplexedConnectionDelegate Build() + { + MultiplexedConnectionDelegate app = features => + { + return Task.CompletedTask; + }; + + foreach (var component in _components.Reverse()) + { + app = component(app); + } + + return app; + } + } +} diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs index 902854532b9e..f2b050b35cc6 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -1,16 +1,74 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.IO.Pipelines; +using System; +using System.Collections.Generic; +using System.Net; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { - public abstract class MultiplexedConnectionContext : ConnectionContext + public abstract class MultiplexedConnectionContext : IAsyncDisposable { - public override IDuplexPipe Transport { get; set; } = null; + /// + /// Gets or sets a unique identifier to represent this connection in trace logs. + /// + public abstract string ConnectionId { get; set; } + + /// + /// Gets the collection of features provided by the server and middleware available on this connection. + /// + public abstract IFeatureCollection Features { get; } + + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this connection. + /// + public abstract IDictionary Items { get; set; } + + /// + /// Triggered when the client connection is closed. + /// + public virtual CancellationToken ConnectionClosed { get; set; } + + /// + /// Gets or sets the local endpoint for this connection. + /// + public virtual EndPoint LocalEndPoint { get; set; } + + /// + /// Gets or sets the remote endpoint for this connection. + /// + public virtual EndPoint RemoteEndPoint { get; set; } + + /// + /// Aborts the underlying connection. + /// + /// An optional describing the reason the connection is being terminated. + public virtual void Abort(ConnectionAbortedException abortReason) + { + // We expect this to be overridden, but this helps maintain back compat + // with implementations of ConnectionContext that predate the addition of + // ConnectionContext.Abort() + Features.Get()?.Abort(); + } + + /// + /// Aborts the underlying connection. + /// + public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); + + /// + /// Releases resources for the underlying connection. + /// + /// A that completes when resources have been released. + public virtual ValueTask DisposeAsync() + { + return default; + } + public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); public abstract ValueTask ConnectAsync(IFeatureCollection features = null, bool unidirectional = false, CancellationToken cancellationToken = default); } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs new file mode 100644 index 000000000000..dd7bfdb1fcc1 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// A function that can process a connection. + /// + /// A representing the connection. + /// A that represents the connection lifetime. When the task completes, the connection will be closed. + public delegate Task MultiplexedConnectionDelegate(MultiplexedConnectionContext connection); +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 8ce851ce5b37..46d8c99e5140 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -34,11 +34,7 @@ internal class Http3Connection : IRequestProcessor public Http3Connection(HttpConnectionContext context) { - if (!(context.ConnectionContext is MultiplexedConnectionContext)) - { - throw new ArgumentException("Must pass an MultiplexedConnectionContext into Http3Connection."); - } - _multiplexedContext = (MultiplexedConnectionContext)context.ConnectionContext; + _multiplexedContext = context.MultiplexedConnectionContext; Context = context; DynamicTable = new DynamicTable(0); } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs index 562b7bd1a965..6e81838d2f11 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Buffers; -using System.Collections.Generic; using System.IO.Pipelines; using System.Net; using Microsoft.AspNetCore.Connections; @@ -11,11 +10,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { + // TODO consider duplicating this and HttpConnection for Http3. internal class HttpConnectionContext { public string ConnectionId { get; set; } public HttpProtocols Protocols { get; set; } public ConnectionContext ConnectionContext { get; set; } + public MultiplexedConnectionContext MultiplexedConnectionContext { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } public MemoryPool MemoryPool { get; set; } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index ca1af168f01c..fb1f35caabca 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -22,8 +22,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core public class KestrelServer : IServer { private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>(); + private readonly List<(IMultiplexedConnectionListener, Task)> _multiplexedTransports = new List<(IMultiplexedConnectionListener, Task)>(); private readonly IServerAddressesFeature _serverAddresses; private readonly List _transportFactories; + private readonly List _multiplexedTransportFactories; private bool _hasStarted; private int _stopping; diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 14e483c403dd..3a40b2315e46 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -15,9 +15,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// Describes either an , Unix domain socket path, or a file descriptor for an already open /// socket that Kestrel should bind to or open. /// - public class ListenOptions : IConnectionBuilder + public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder { internal readonly List> _middleware = new List>(); + internal readonly List> _multiplexedMiddleware = new List>(); internal ListenOptions(IPEndPoint endPoint) { @@ -123,6 +124,12 @@ public IConnectionBuilder Use(Func middl return this; } + public IMultiplexedConnectionBuilder UseMultiplexed(Func middleware) + { + _multiplexedMiddleware.Add(middleware); + return this; + } + public ConnectionDelegate Build() { ConnectionDelegate app = context => @@ -139,6 +146,22 @@ public ConnectionDelegate Build() return app; } + public MultiplexedConnectionDelegate BuildMultiplexed() + { + MultiplexedConnectionDelegate app = context => + { + return Task.CompletedTask; + }; + + for (int i = _multiplexedMiddleware.Count - 1; i >= 0; i--) + { + var component = _multiplexedMiddleware[i]; + app = component(app); + } + + return app; + } + internal virtual async Task BindAsync(AddressBindContext context) { await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false); diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs new file mode 100644 index 000000000000..292ab0db1b59 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting.Server; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class HttpMultiplexedConnectionMiddleware + { + private readonly ServiceContext _serviceContext; + private readonly IHttpApplication _application; + private readonly HttpProtocols _protocols; + + public HttpMultiplexedConnectionMiddleware(ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) + { + _serviceContext = serviceContext; + _application = application; + _protocols = protocols; + } + + public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) + { + var memoryPoolFeature = connectionContext.Features.Get(); + + var httpConnectionContext = new HttpConnectionContext + { + ConnectionId = connectionContext.ConnectionId, + MultiplexedConnectionContext = connectionContext, + Protocols = _protocols, + ServiceContext = _serviceContext, + ConnectionFeatures = connectionContext.Features, + MemoryPool = memoryPoolFeature.MemoryPool, + LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint, + RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint + }; + + var connection = new HttpConnection(httpConnectionContext); + + return connection.ProcessRequestsAsync(_application); + } + } +} diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index 2190f0cb000c..98e2490ca230 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -45,25 +45,18 @@ public static void Main(string[] args) options.Listen(IPAddress.Any, basePort, listenOptions => { listenOptions.Protocols = HttpProtocols.Http3; - listenOptions.Use((next) => + listenOptions.UseMultiplexed((next) => { return async connection => { - if (connection is MultiplexedConnectionContext) + while (true) { - while (true) + var streamContext = await connection.AcceptAsync(); + if (streamContext == null) { - var streamContext = await ((MultiplexedConnectionContext)connection).AcceptAsync(); - if (streamContext == null) - { - return; - } - _ = next(streamContext); + return; } - } - else - { - await next(connection); + _ = next(streamContext); } }; }); diff --git a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs index f6bc0bfab660..050d346e2e40 100644 --- a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs @@ -25,7 +25,7 @@ static async Task Main(string[] args) }) .ConfigureServices(services => { - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddOptions(); services.Configure((options) => @@ -42,9 +42,9 @@ static async Task Main(string[] args) private class QuicClientService { - private readonly IConnectionFactory _connectionFactory; + private readonly IMultiplexedConnectionFactory _connectionFactory; private readonly ILogger _logger; - public QuicClientService(IConnectionFactory connectionFactory, ILogger logger) + public QuicClientService(IMultiplexedConnectionFactory connectionFactory, ILogger logger) { _connectionFactory = connectionFactory; _logger = logger; @@ -54,7 +54,7 @@ public async Task RunAsync() { Console.WriteLine("Starting"); var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555)); - var streamContext = await (connectionContext as MultiplexedConnectionContext).ConnectAsync(); + var streamContext = await connectionContext.ConnectAsync(); Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, args) => { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index b9793e339640..9307a5170c9c 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -97,7 +98,7 @@ protected void CreateConnection() var httpConnectionContext = new HttpConnectionContext { - ConnectionContext = _multiplexedContext, + MultiplexedConnectionContext = _multiplexedContext, ConnectionFeatures = features, ServiceContext = _serviceContext, MemoryPool = _memoryPool, diff --git a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs index fc3d9bc818b3..7047249a8470 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs @@ -71,6 +71,7 @@ public static void Run( UpdateFile(knownHeadersPath, knownHeadersContent); UpdateFile(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); UpdateFile(httpUtilitiesPath, httpUtilitiesContent); + UpdateFile(http2ConnectionPath, http2ConnectionContent); UpdateFile(transportMultiplexedConnectionFeatureCollectionPath, transportMultiplexedConnectionFeatureCollectionContent); UpdateFile(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); UpdateFile(transportStreamFeatureCollectionPath, transportStreamFeatureCollectionContent); @@ -84,10 +85,10 @@ public static void UpdateFile(string path, string content) File.WriteAllText(path, content); } - var existingHttp2Connection = File.Exists(http2ConnectionPath) ? File.ReadAllText(http2ConnectionPath) : ""; - if (!string.Equals(http2ConnectionContent, existingHttp2Connection)) + var existingHttp2Connection = File.Exists(path) ? File.ReadAllText(path) : ""; + if (!string.Equals(content, existingHttp2Connection)) { - File.WriteAllText(http2ConnectionPath, http2ConnectionContent); + File.WriteAllText(path, content); } } } From 1d73f5c51e5882fc8785889d6e9a84f3c1bde24c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 11 Dec 2019 17:45:49 -0800 Subject: [PATCH 04/27] more middleware --- .../src/IConnectionListener.cs | 13 +-------- .../src/IConnectionListenerBase.cs | 28 +++++++++++++++++++ .../src/IMulitplexedConnectionListener.cs | 14 +--------- src/Servers/Kestrel/Core/src/KestrelServer.cs | 28 +++++++++++++++++-- .../Middleware/Http1ConnectionMiddleware.cs | 12 ++++++++ ...leware.cs => Http3ConnectionMiddleware.cs} | 6 ++-- .../HttpConnectionBuilderExtensions.cs | 22 +++++++++++++-- 7 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs create mode 100644 src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs rename src/Servers/Kestrel/Core/src/Middleware/{HttpMultiplexedConnectionMiddleware.cs => Http3ConnectionMiddleware.cs} (84%) diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 464a8650f9b7..4f1a180f79d5 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -11,12 +11,8 @@ namespace Microsoft.AspNetCore.Connections /// /// Defines an interface that represents a listener bound to a specific . /// - public interface IConnectionListener : IAsyncDisposable + public interface IConnectionListener : IConnectionListenerBase { - /// - /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. - /// - EndPoint EndPoint { get; } /// /// Begins an asynchronous operation to accept an incoming connection. @@ -24,12 +20,5 @@ public interface IConnectionListener : IAsyncDisposable /// The token to monitor for cancellation requests. /// A that completes when a connection is accepted, yielding the representing the connection. ValueTask AcceptAsync(CancellationToken cancellationToken = default); - - /// - /// Stops listening for incoming connections. - /// - /// The token to monitor for cancellation requests. - /// A that represents the un-bind operation. - ValueTask UnbindAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs new file mode 100644 index 000000000000..133156542214 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + /// + /// Defines an interface that represents a listener bound to a specific . + /// + public interface IConnectionListenerBase : IAsyncDisposable + { + /// + /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. + /// + EndPoint EndPoint { get; } + + /// + /// Stops listening for incoming connections. + /// + /// The token to monitor for cancellation requests. + /// A that represents the un-bind operation. + ValueTask UnbindAsync(CancellationToken cancellationToken = default); + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs index d83133f31728..4f569d38c1dd 100644 --- a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs @@ -11,25 +11,13 @@ namespace Microsoft.AspNetCore.Connections /// /// Defines an interface that represents a listener bound to a specific . /// - public interface IMultiplexedConnectionListener : IAsyncDisposable + public interface IMultiplexedConnectionListener : IConnectionListenerBase { - /// - /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. - /// - EndPoint EndPoint { get; } - /// /// Begins an asynchronous operation to accept an incoming connection. /// /// The token to monitor for cancellation requests. /// A that completes when a connection is accepted, yielding the representing the connection. ValueTask AcceptAsync(CancellationToken cancellationToken = default); - - /// - /// Stops listening for incoming connections. - /// - /// The token to monitor for cancellation requests. - /// A that represents the un-bind operation. - ValueTask UnbindAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index fb1f35caabca..b7a20338e8fa 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -37,7 +37,7 @@ public KestrelServer(IOptions options, IEnumerable transportFactories, ServiceContext serviceContext) + internal KestrelServer(IEnumerable transportFactories, IEnumerable multiplexedFactories, ServiceContext serviceContext) { if (transportFactories == null) { @@ -132,9 +132,27 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions options) { // Add the HTTP middleware as the terminal connection middleware - options.UseHttpServer(ServiceContext, application, options.Protocols); + if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1) + { + options.UseHttp1Server(ServiceContext, application, options.Protocols); + var connectionDelegate = options.Build(); + } - var connectionDelegate = options.Build(); + if ((options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2) + { + options.UseHttp2Server(ServiceContext, application, options.Protocols); + var connectionDelegate = options.Build(); + } + + if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) + { + options.UseHttp3Server(ServiceContext, application, options.Protocols); + var connectionDelegate = options.Build(); + } + + // Add the HTTP middleware as the terminal connection middleware + + var multiplxedConnectionDelegate = options.BuildMultiplexed(); // Add the connection limit middleware if (Options.Limits.MaxConcurrentConnections.HasValue) @@ -174,7 +192,11 @@ async Task OnBind(ListenOptions options) var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false); + var multiplexedFactory = _multiplexedTransportFactories.Last(); + var multiplexedTransport = await multiplexedFactory.BindAsync(options.EndPoint).ConfigureAwait(false); + // Update the endpoint + // TODO I don't know if it makes sense to have two factories options.EndPoint = transport.EndPoint; var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); diff --git a/src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs new file mode 100644 index 000000000000..5813860dd2b8 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Middleware +{ + class Http1ConnectionMiddleware + { + } +} diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs similarity index 84% rename from src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs rename to src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs index 292ab0db1b59..a8cf147c5352 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs @@ -9,17 +9,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - internal class HttpMultiplexedConnectionMiddleware + internal class Http3ConnectionMiddleware { private readonly ServiceContext _serviceContext; private readonly IHttpApplication _application; - private readonly HttpProtocols _protocols; - public HttpMultiplexedConnectionMiddleware(ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) + public Http3ConnectionMiddleware(ServiceContext serviceContext, IHttpApplication application) { _serviceContext = serviceContext; _application = application; - _protocols = protocols; } public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs index e46a2c2a831e..37a709170b5f 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs @@ -10,13 +10,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { internal static class HttpConnectionBuilderExtensions { - public static IConnectionBuilder UseHttpServer(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) + public static IConnectionBuilder UseHttp1Server(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application) { - var middleware = new HttpConnectionMiddleware(serviceContext, application, protocols); + var middleware = new Http1ConnectionMiddleware(serviceContext, application, protocols); return builder.Use(next => { return middleware.OnConnectionAsync; }); } + + public static IConnectionBuilder UseHttp2Server(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application) + { + var middleware = new Http2ConnectionMiddleware(serviceContext, application, protocols); + return builder.Use(next => + { + return middleware.OnConnectionAsync; + }); + } + + public static IMultiplexedConnectionBuilder UseHttp3Server(this IMultiplexedConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) + { + var middleware = new Http3ConnectionMiddleware(serviceContext, application); + return builder.UseMultiplexed(next => + { + return middleware.OnConnectionAsync; + }); + } } } From baa3598e517276fa62050bf0a51ff0f8e9d5e289 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 11 Dec 2019 19:38:49 -0800 Subject: [PATCH 05/27] Almost got things working, need to fix KestrelConnection next. Base class is the best way to move forward here. --- .../src/ConnectionBuilderExtensions.cs | 2 +- .../src/MultiplexedConnectionBuilder.cs | 4 +- .../MultiplexedConnectionBuilderExtensions.cs | 34 +++ .../src/Internal/Http3/Http3Connection.cs | 8 +- .../src/Internal/Http3ConnectionContext.cs | 24 ++ .../Core/src/Internal/Http3ConnectionTemp.cs | 233 ++++++++++++++++++ .../Core/src/Internal/HttpConnection.cs | 9 - .../src/Internal/HttpConnectionContext.cs | 2 - .../MultiplexedConnectionDispatcher.cs | 78 ++++++ src/Servers/Kestrel/Core/src/KestrelServer.cs | 60 +++-- .../Middleware/Http1ConnectionMiddleware.cs | 12 - .../Middleware/Http3ConnectionMiddleware.cs | 5 +- .../HttpConnectionBuilderExtensions.cs | 15 +- .../Kestrel/samples/QuicSampleApp/Program.cs | 49 ++-- .../Http3/Http3TestBase.cs | 5 +- 15 files changed, 442 insertions(+), 98 deletions(-) create mode 100644 src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs create mode 100644 src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs create mode 100644 src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs create mode 100644 src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs delete mode 100644 src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs diff --git a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs index 100917b0094a..55b0311eb977 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs @@ -40,4 +40,4 @@ public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder, }); } } -} \ No newline at end of file +} diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs index 202f29df5eb5..fba04e05af4f 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs @@ -19,13 +19,13 @@ public MultiplexedConnectionBuilder(IServiceProvider applicationServices) ApplicationServices = applicationServices; } - public IMultiplexedConnectionBuilder Use(Func middleware) + public IMultiplexedConnectionBuilder UseMultiplexed(Func middleware) { _components.Add(middleware); return this; } - public MultiplexedConnectionDelegate Build() + public MultiplexedConnectionDelegate BuildMultiplexed() { MultiplexedConnectionDelegate app = features => { diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs new file mode 100644 index 000000000000..25492eab5e43 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Connections +{ + public static class MultiplexedConnectionBuilderExtensions + { + public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func, Task> middleware) + { + return connectionBuilder.UseMultiplexed(next => + { + return context => + { + Func simpleNext = () => next(context); + return middleware(context, simpleNext); + }; + }); + } + + public static IMultiplexedConnectionBuilder RunMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) + { + return connectionBuilder.UseMultiplexed(next => + { + return context => + { + return middleware(context); + }; + }); + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 46d8c99e5140..f7b48bc65954 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 { internal class Http3Connection : IRequestProcessor { - public HttpConnectionContext Context { get; private set; } + public Http3ConnectionContext Context { get; private set; } public DynamicTable DynamicTable { get; set; } @@ -32,7 +32,7 @@ internal class Http3Connection : IRequestProcessor private MultiplexedConnectionContext _multiplexedContext; //private volatile bool _haveSentGoAway; - public Http3Connection(HttpConnectionContext context) + public Http3Connection(Http3ConnectionContext context) { _multiplexedContext = context.MultiplexedConnectionContext; Context = context; @@ -75,7 +75,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl { ConnectionId = streamContext.ConnectionId, ConnectionContext = streamContext, - Protocols = Context.Protocols, + Protocols = HttpProtocols.Http3, ServiceContext = Context.ServiceContext, ConnectionFeatures = streamContext.Features, MemoryPool = Context.MemoryPool, @@ -153,7 +153,7 @@ private async ValueTask CreateNewUnidirectionalStreamAsync MemoryPool { get; set; } + public IPEndPoint LocalEndPoint { get; set; } + public IPEndPoint RemoteEndPoint { get; set; } + public ITimeoutControl TimeoutControl { get; set; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs new file mode 100644 index 000000000000..89e292ecb190 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs @@ -0,0 +1,233 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class Http3ConnectionTemp : ITimeoutHandler + { + // Use C#7.3's ReadOnlySpan optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/ + private static ReadOnlySpan Http2Id => new[] { (byte)'h', (byte)'2' }; + + private readonly Http3ConnectionContext _context; + private readonly ISystemClock _systemClock; + private readonly TimeoutControl _timeoutControl; + + private readonly object _protocolSelectionLock = new object(); + private ProtocolSelectionState _protocolSelectionState = ProtocolSelectionState.Initializing; + private IRequestProcessor _requestProcessor; + private Http1Connection _http1Connection; + + public Http3ConnectionTemp(Http3ConnectionContext context) + { + _context = context; + _systemClock = _context.ServiceContext.SystemClock; + + _timeoutControl = new TimeoutControl(this); + + // Tests override the timeout control sometimes + _context.TimeoutControl ??= _timeoutControl; + } + + private IKestrelTrace Log => _context.ServiceContext.Log; + + public async Task ProcessRequestsAsync(IHttpApplication httpApplication) + { + try + { + // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. + _timeoutControl.Initialize(_systemClock.UtcNowTicks); + + IRequestProcessor requestProcessor = null; + + requestProcessor = new Http3Connection(_context); + _protocolSelectionState = ProtocolSelectionState.Selected; + + _requestProcessor = requestProcessor; + + if (requestProcessor != null) + { + var connectionHeartbeatFeature = _context.ConnectionFeatures.Get(); + var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get(); + + // These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel, + // we'll need to handle these missing. + Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!"); + Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!"); + + // Register the various callbacks once we're going to start processing requests + + // The heart beat for various timeouts + connectionHeartbeatFeature?.OnHeartbeat(state => ((Http3ConnectionTemp)state).Tick(), this); + + // Register for graceful shutdown of the server + using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((Http3ConnectionTemp)state).StopProcessingNextRequest(), this); + + // Register for connection close + using var closedRegistration = _context.MultiplexedConnectionContext.ConnectionClosed.Register(state => ((Http3ConnectionTemp)state).OnConnectionClosed(), this); + + await requestProcessor.ProcessRequestsAsync(httpApplication); + } + } + catch (Exception ex) + { + Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}."); + } + finally + { + if (_http1Connection?.IsUpgraded == true) + { + _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); + } + } + } + + // For testing only + internal void Initialize(IRequestProcessor requestProcessor) + { + _requestProcessor = requestProcessor; + _http1Connection = requestProcessor as Http1Connection; + _protocolSelectionState = ProtocolSelectionState.Selected; + } + + private void StopProcessingNextRequest() + { + ProtocolSelectionState previousState; + lock (_protocolSelectionLock) + { + previousState = _protocolSelectionState; + Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing"); + + switch (_protocolSelectionState) + { + case ProtocolSelectionState.Selected: + case ProtocolSelectionState.Aborted: + break; + } + } + + switch (previousState) + { + case ProtocolSelectionState.Selected: + _requestProcessor.StopProcessingNextRequest(); + break; + case ProtocolSelectionState.Aborted: + break; + } + } + + private void OnConnectionClosed() + { + ProtocolSelectionState previousState; + lock (_protocolSelectionLock) + { + previousState = _protocolSelectionState; + Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing"); + + switch (_protocolSelectionState) + { + case ProtocolSelectionState.Selected: + case ProtocolSelectionState.Aborted: + break; + } + } + + switch (previousState) + { + case ProtocolSelectionState.Selected: + _requestProcessor.OnInputOrOutputCompleted(); + break; + case ProtocolSelectionState.Aborted: + break; + } + } + + private void Abort(ConnectionAbortedException ex) + { + ProtocolSelectionState previousState; + + lock (_protocolSelectionLock) + { + previousState = _protocolSelectionState; + Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing"); + + _protocolSelectionState = ProtocolSelectionState.Aborted; + } + + switch (previousState) + { + case ProtocolSelectionState.Selected: + _requestProcessor.Abort(ex); + break; + case ProtocolSelectionState.Aborted: + break; + } + } + + + private void Tick() + { + if (_protocolSelectionState == ProtocolSelectionState.Aborted) + { + // It's safe to check for timeouts on a dead connection, + // but try not to in order to avoid extraneous logs. + return; + } + + // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat. + var now = _systemClock.UtcNowUnsynchronized; + _timeoutControl.Tick(now); + _requestProcessor.Tick(now); + } + + public void OnTimeout(TimeoutReason reason) + { + // In the cases that don't log directly here, we expect the setter of the timeout to also be the input + // reader, so when the read is canceled or aborted, the reader should write the appropriate log. + switch (reason) + { + case TimeoutReason.KeepAlive: + _requestProcessor.StopProcessingNextRequest(); + break; + case TimeoutReason.RequestHeaders: + _requestProcessor.HandleRequestHeadersTimeout(); + break; + case TimeoutReason.ReadDataRate: + _requestProcessor.HandleReadDataRateTimeout(); + break; + case TimeoutReason.WriteDataRate: + Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection?.TraceIdentifier); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)); + break; + case TimeoutReason.RequestBodyDrain: + case TimeoutReason.TimeoutFeature: + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer)); + break; + default: + Debug.Assert(false, "Invalid TimeoutReason"); + break; + } + } + + private enum ProtocolSelectionState + { + Initializing, + Selected, + Aborted + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 5c92ae816dd0..a00f6002a485 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -68,10 +68,6 @@ public async Task ProcessRequestsAsync(IHttpApplication http requestProcessor = new Http2Connection(_context); _protocolSelectionState = ProtocolSelectionState.Selected; break; - case HttpProtocols.Http3: - requestProcessor = new Http3Connection(_context); - _protocolSelectionState = ProtocolSelectionState.Selected; - break; case HttpProtocols.None: // An error was already logged in SelectProtocol(), but we should close the connection. break; @@ -204,11 +200,6 @@ private void Abort(ConnectionAbortedException ex) private HttpProtocols SelectProtocol() { - if (_context.Protocols == HttpProtocols.Http3) - { - return HttpProtocols.Http3; - } - var hasTls = _context.ConnectionFeatures.Get() != null; var applicationProtocol = _context.ConnectionFeatures.Get()?.ApplicationProtocol ?? new ReadOnlyMemory(); diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs index 6e81838d2f11..69c46ec79c87 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs @@ -10,13 +10,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - // TODO consider duplicating this and HttpConnection for Http3. internal class HttpConnectionContext { public string ConnectionId { get; set; } public HttpProtocols Protocols { get; set; } public ConnectionContext ConnectionContext { get; set; } - public MultiplexedConnectionContext MultiplexedConnectionContext { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } public MemoryPool MemoryPool { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs new file mode 100644 index 000000000000..8fa70811929a --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class MultiplexedConnectionDispatcher + { + private static long _lastConnectionId = long.MinValue; + + private readonly ServiceContext _serviceContext; + private readonly MultiplexedConnectionDelegate _connectionDelegate; + private readonly TaskCompletionSource _acceptLoopTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + public MultiplexedConnectionDispatcher(ServiceContext serviceContext, MultiplexedConnectionDelegate connectionDelegate) + { + _serviceContext = serviceContext; + _connectionDelegate = connectionDelegate; + } + + private IKestrelTrace Log => _serviceContext.Log; + + public Task StartAcceptingConnections(IMultiplexedConnectionListener listener) + { + ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false); + return _acceptLoopTcs.Task; + } + + private void StartAcceptingConnectionsCore(IMultiplexedConnectionListener listener) + { + // REVIEW: Multiple accept loops in parallel? + _ = AcceptConnectionsAsync(); + + async Task AcceptConnectionsAsync() + { + try + { + while (true) + { + var connection = await listener.AcceptAsync(); + + if (connection == null) + { + // We're done listening + break; + } + + // Add the connection to the connection manager before we queue it for execution + var id = Interlocked.Increment(ref _lastConnectionId); + // TODO Don't pass null in here! use a base class + var kestrelConnection = new KestrelConnection(id, _serviceContext, null, null, Log); + + _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection); + + Log.ConnectionAccepted(connection.ConnectionId); + + ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false); + } + } + catch (Exception ex) + { + // REVIEW: If the accept loop ends should this trigger a server shutdown? It will manifest as a hang + Log.LogCritical(0, ex, "The connection listener failed to accept any new connections."); + } + finally + { + _acceptLoopTcs.TrySetResult(null); + } + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index b7a20338e8fa..7ff4e001fc8a 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -25,14 +25,24 @@ public class KestrelServer : IServer private readonly List<(IMultiplexedConnectionListener, Task)> _multiplexedTransports = new List<(IMultiplexedConnectionListener, Task)>(); private readonly IServerAddressesFeature _serverAddresses; private readonly List _transportFactories; - private readonly List _multiplexedTransportFactories; + private readonly List _multiplexedTransportFactories; private bool _hasStarted; private int _stopping; private readonly TaskCompletionSource _stoppedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public KestrelServer(IOptions options, IEnumerable transportFactories, ILoggerFactory loggerFactory) - : this(transportFactories, CreateServiceContext(options, loggerFactory)) + : this(transportFactories, null, CreateServiceContext(options, loggerFactory)) + { + } + public KestrelServer(IOptions options, IEnumerable transportFactories, IEnumerable multiplexedFactories, ILoggerFactory loggerFactory) + : this(transportFactories, multiplexedFactories, CreateServiceContext(options, loggerFactory)) + { + } + + // For testing + internal KestrelServer(IEnumerable transportFactories, ServiceContext serviceContext) + : this(transportFactories, null, serviceContext) { } @@ -45,6 +55,7 @@ internal KestrelServer(IEnumerable transportFactorie } _transportFactories = transportFactories.ToList(); + _multiplexedTransportFactories = multiplexedFactories?.ToList(); if (_transportFactories.Count == 0) { @@ -132,32 +143,41 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions options) { // Add the HTTP middleware as the terminal connection middleware - if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1) + if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1 + || (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2) { - options.UseHttp1Server(ServiceContext, application, options.Protocols); + options.UseHttpServer(ServiceContext, application, options.Protocols); var connectionDelegate = options.Build(); - } - if ((options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2) - { - options.UseHttp2Server(ServiceContext, application, options.Protocols); - var connectionDelegate = options.Build(); - } + // Add the connection limit middleware + if (Options.Limits.MaxConcurrentConnections.HasValue) + { + connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; + } - if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) - { - options.UseHttp3Server(ServiceContext, application, options.Protocols); - var connectionDelegate = options.Build(); - } + var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); + var factory = _transportFactories.Last(); + var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false); - // Add the HTTP middleware as the terminal connection middleware + var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); - var multiplxedConnectionDelegate = options.BuildMultiplexed(); + _transports.Add((transport, acceptLoopTask)); + options.EndPoint = transport.EndPoint; + } - // Add the connection limit middleware - if (Options.Limits.MaxConcurrentConnections.HasValue) + if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { - connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; + options.UseHttp3Server(ServiceContext, application, options.Protocols); + var multiplxedConnectionDelegate = options.BuildMultiplexed(); + var multiplexedFactory = _multiplexedTransportFactories.Last(); + var multiplexedTransport = await multiplexedFactory.BindAsync(options.EndPoint).ConfigureAwait(false); + var connectionDispatcher = new MultiplexedConnectionDispatcher(ServiceContext, multiplxedConnectionDelegate); + var factory = _multiplexedTransportFactories.Last(); + var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false); + var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); + _multiplexedTransports.Add((transport, acceptLoopTask)); + + options.EndPoint = multiplexedTransport.EndPoint; } var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); diff --git a/src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs deleted file mode 100644 index 5813860dd2b8..000000000000 --- a/src/Servers/Kestrel/Core/src/Middleware/Http1ConnectionMiddleware.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Middleware -{ - class Http1ConnectionMiddleware - { - } -} diff --git a/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs index a8cf147c5352..1b18ddf5c8ee 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs @@ -24,11 +24,10 @@ public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) { var memoryPoolFeature = connectionContext.Features.Get(); - var httpConnectionContext = new HttpConnectionContext + var http3ConnectionContext = new Http3ConnectionContext { ConnectionId = connectionContext.ConnectionId, MultiplexedConnectionContext = connectionContext, - Protocols = _protocols, ServiceContext = _serviceContext, ConnectionFeatures = connectionContext.Features, MemoryPool = memoryPoolFeature.MemoryPool, @@ -36,7 +35,7 @@ public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint }; - var connection = new HttpConnection(httpConnectionContext); + var connection = new Http3ConnectionTemp(http3ConnectionContext); return connection.ProcessRequestsAsync(_application); } diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs index 37a709170b5f..33d8356db073 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Connections; @@ -10,18 +8,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { internal static class HttpConnectionBuilderExtensions { - public static IConnectionBuilder UseHttp1Server(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application) + public static IConnectionBuilder UseHttpServer(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { - var middleware = new Http1ConnectionMiddleware(serviceContext, application, protocols); - return builder.Use(next => - { - return middleware.OnConnectionAsync; - }); - } - - public static IConnectionBuilder UseHttp2Server(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application) - { - var middleware = new Http2ConnectionMiddleware(serviceContext, application, protocols); + var middleware = new HttpConnectionMiddleware(serviceContext, application, protocols); return builder.Use(next => { return middleware.OnConnectionAsync; diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index 98e2490ca230..96d680e5e2ac 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -45,46 +45,37 @@ public static void Main(string[] args) options.Listen(IPAddress.Any, basePort, listenOptions => { listenOptions.Protocols = HttpProtocols.Http3; - listenOptions.UseMultiplexed((next) => - { - return async connection => - { - while (true) - { - var streamContext = await connection.AcceptAsync(); - if (streamContext == null) - { - return; - } - _ = next(streamContext); - } - }; - }); - async Task EchoServer(ConnectionContext connection) + async Task EchoServer(MultiplexedConnectionContext connection) { // For graceful shutdown - try + + while (true) { - while (true) + var stream = await connection.AcceptAsync(); + try { - var result = await connection.Transport.Input.ReadAsync(); - - if (result.IsCompleted) + while (true) { - break; - } + var result = await stream.Transport.Input.ReadAsync(); + + if (result.IsCompleted) + { + break; + } - await connection.Transport.Output.WriteAsync(result.Buffer.ToArray()); + await stream.Transport.Output.WriteAsync(result.Buffer.ToArray()); - connection.Transport.Input.AdvanceTo(result.Buffer.End); + stream.Transport.Input.AdvanceTo(result.Buffer.End); + } + } + catch (OperationCanceledException) + { } - } - catch (OperationCanceledException) - { } } - listenOptions.Run(EchoServer); + + listenOptions.RunMultiplexed(EchoServer); }); }) .UseStartup(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index 9307a5170c9c..63dcd5e062f3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -96,18 +96,17 @@ protected void CreateConnection() _multiplexedContext = new TestMultiplexedConnectionContext(this); - var httpConnectionContext = new HttpConnectionContext + var httpConnectionContext = new Http3ConnectionContext { MultiplexedConnectionContext = _multiplexedContext, ConnectionFeatures = features, ServiceContext = _serviceContext, MemoryPool = _memoryPool, - Transport = null, // Make sure it's null TimeoutControl = _mockTimeoutControl.Object }; _connection = new Http3Connection(httpConnectionContext); - var httpConnection = new HttpConnection(httpConnectionContext); + var httpConnection = new Http3ConnectionTemp(httpConnectionContext); httpConnection.Initialize(_connection); _mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny())) .Callback(r => httpConnection.OnTimeout(r)); From cfa055d728cf33581f01e837f4b90d49609273b7 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 13 Feb 2020 12:47:24 -0800 Subject: [PATCH 06/27] Compiling --- .../src/Features/IQuicStreamFeature.cs | 3 +- .../src/IConnectionListener.cs | 13 +- .../src/IConnectionListenerBase.cs | 28 --- .../src/IConnectionListenerFactory.cs | 1 + .../src/IMulitplexedConnectionListener.cs | 14 +- .../src/IMultiplexedConnectionFactory.cs | 1 - .../IMultiplexedConnectionListenerFactory.cs | 4 +- .../src/MultiplexedConnectionContext.cs | 3 +- .../src/MultiplexedConnectionDelegate.cs | 6 +- .../src/StreamContext.cs | 28 +-- .../src/Internal/Http3/Http3Connection.cs | 227 ++++++++++++++--- .../Core/src/Internal/Http3/Http3Stream.cs | 4 +- .../Core/src/Internal/Http3/Http3StreamOfT.cs | 2 +- .../src/Internal/Http3ConnectionContext.cs | 1 - .../Core/src/Internal/Http3ConnectionTemp.cs | 233 ------------------ .../Core/src/Internal/Http3StreamContext.cs | 17 ++ .../Infrastructure/KestrelEventSource.cs | 22 ++ .../MultiplexedConnectionManager.cs | 110 +++++++++ .../MultiplexedConnectionReference.cs | 25 ++ .../MultiplexedKestrelConnection.cs | 230 +++++++++++++++++ .../MultiplexedConnectionDispatcher.cs | 4 +- .../Core/src/Internal/ServiceContext.cs | 2 + src/Servers/Kestrel/Core/src/KestrelServer.cs | 63 ++--- .../Middleware/Http3ConnectionMiddleware.cs | 3 +- .../src/Internal/QuicConnectionContext.cs | 46 ++-- .../src/Internal/QuicConnectionListener.cs | 16 +- .../src/Internal/QuicStreamContext.cs | 7 +- .../src/QuicConnectionFactory.cs | 5 +- .../src/QuicTransportFactory.cs | 6 +- .../src/QuicTransportOptions.cs | 5 - .../Kestrel/samples/Http3SampleApp/Program.cs | 68 +++-- .../Kestrel/samples/QuicSampleApp/Program.cs | 24 +- .../Http3/Http3TestBase.cs | 34 ++- 33 files changed, 764 insertions(+), 491 deletions(-) delete mode 100644 src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs create mode 100644 src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs diff --git a/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs index 802209c1ea78..66f706dbf912 100644 --- a/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs +++ b/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs @@ -3,10 +3,9 @@ namespace Microsoft.AspNetCore.Connections.Features { - public interface IQuicStreamFeature + public interface IStreamDirectionFeature { bool CanRead { get; } bool CanWrite { get; } - long StreamId { get; } } } diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 4f1a180f79d5..8295c9f93d46 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -11,8 +11,19 @@ namespace Microsoft.AspNetCore.Connections /// /// Defines an interface that represents a listener bound to a specific . /// - public interface IConnectionListener : IConnectionListenerBase + public interface IConnectionListener : IAsyncDisposable { + /// + /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. + /// + EndPoint EndPoint { get; } + + /// + /// Stops listening for incoming connections. + /// + /// The token to monitor for cancellation requests. + /// A that represents the un-bind operation. + ValueTask UnbindAsync(CancellationToken cancellationToken = default); /// /// Begins an asynchronous operation to accept an incoming connection. diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs deleted file mode 100644 index 133156542214..000000000000 --- a/src/Servers/Connections.Abstractions/src/IConnectionListenerBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Connections -{ - /// - /// Defines an interface that represents a listener bound to a specific . - /// - public interface IConnectionListenerBase : IAsyncDisposable - { - /// - /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. - /// - EndPoint EndPoint { get; } - - /// - /// Stops listening for incoming connections. - /// - /// The token to monitor for cancellation requests. - /// A that represents the un-bind operation. - ValueTask UnbindAsync(CancellationToken cancellationToken = default); - } -} diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs index 2bcfa5ebca84..dd8843f7d3f0 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs @@ -4,6 +4,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { diff --git a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs index 4f569d38c1dd..b4d9bcf80c48 100644 --- a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs @@ -11,8 +11,20 @@ namespace Microsoft.AspNetCore.Connections /// /// Defines an interface that represents a listener bound to a specific . /// - public interface IMultiplexedConnectionListener : IConnectionListenerBase + public interface IMultiplexedConnectionListener : IAsyncDisposable { + /// + /// The endpoint that was bound. This may differ from the requested endpoint, such as when the caller requested that any free port be selected. + /// + EndPoint EndPoint { get; } + + /// + /// Stops listening for incoming connections. + /// + /// The token to monitor for cancellation requests. + /// A that represents the un-bind operation. + ValueTask UnbindAsync(CancellationToken cancellationToken = default); + /// /// Begins an asynchronous operation to accept an incoming connection. /// diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs index 83c2bc4f9d11..a3f69f7a6854 100644 --- a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs index 7da3636db674..3b5010beda33 100644 --- a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionListenerFactory.cs @@ -4,6 +4,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { @@ -16,8 +17,9 @@ public interface IMultiplexedConnectionListenerFactory /// Creates an bound to the specified . /// /// The to bind to. + /// A feature collection to pass options when binding. /// The token to monitor for cancellation requests. /// A that completes when the listener has been bound, yielding a representing the new listener. - ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default); + ValueTask BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs index f2b050b35cc6..35c6eef506bf 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -70,6 +70,7 @@ public virtual ValueTask DisposeAsync() } public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); - public abstract ValueTask ConnectAsync(IFeatureCollection features = null, bool unidirectional = false, CancellationToken cancellationToken = default); + + public abstract ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs index dd7bfdb1fcc1..c85298ea2d9b 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionDelegate.cs @@ -1,6 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System.Threading.Tasks; namespace Microsoft.AspNetCore.Connections diff --git a/src/Servers/Connections.Abstractions/src/StreamContext.cs b/src/Servers/Connections.Abstractions/src/StreamContext.cs index 9b3bf4ef7e8b..4dd455e98327 100644 --- a/src/Servers/Connections.Abstractions/src/StreamContext.cs +++ b/src/Servers/Connections.Abstractions/src/StreamContext.cs @@ -1,33 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Connections { public abstract class StreamContext : ConnectionContext { /// - /// Triggered when the client stream is closed. + /// Gets the id assigned to the stream. /// - public virtual CancellationToken StreamClosed { get; set; } - - /// - /// Gets or sets a unique identifier to represent this stream in trace logs. - /// - public abstract long StreamId { get; set; } - - /// - /// Represents the direction - /// - public abstract Direction Direction { get; } - } - - public enum Direction - { - BidirectionalInbound, - BidirectionalOutbound, - UnidirectionalInbound, - UnidirectionalOutbound + public abstract long StreamId { get; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index f7b48bc65954..3f2839032457 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -3,21 +3,24 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.Net; using System.Net.Http; +using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 { - internal class Http3Connection : IRequestProcessor + internal class Http3Connection : IRequestProcessor, ITimeoutHandler { - public Http3ConnectionContext Context { get; private set; } - public DynamicTable DynamicTable { get; set; } public Http3ControlStream ControlStream { get; set; } @@ -31,12 +34,20 @@ internal class Http3Connection : IRequestProcessor private object _sync = new object(); private MultiplexedConnectionContext _multiplexedContext; //private volatile bool _haveSentGoAway; + private readonly Http3ConnectionContext _context; + private readonly ISystemClock _systemClock; + private readonly TimeoutControl _timeoutControl; + private bool _aborted; + private object _protocolSelectionLock = new object(); public Http3Connection(Http3ConnectionContext context) { _multiplexedContext = context.MultiplexedConnectionContext; - Context = context; + _context = context; DynamicTable = new DynamicTable(0); + _systemClock = context.ServiceContext.SystemClock; + _timeoutControl = new TimeoutControl(this); + _context.TimeoutControl ??= _timeoutControl; } internal long HighestStreamId @@ -54,7 +65,166 @@ internal long HighestStreamId } } - public async Task ProcessRequestsAsync(IHttpApplication application) + private IKestrelTrace Log => _context.ServiceContext.Log; + + public async Task ProcessRequestsAsync(IHttpApplication httpApplication) + { + try + { + // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. + _timeoutControl.Initialize(_systemClock.UtcNowTicks); + + var connectionHeartbeatFeature = _context.ConnectionFeatures.Get(); + var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get(); + + // These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel, + // we'll need to handle these missing. + Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!"); + Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!"); + + // Register the various callbacks once we're going to start processing requests + + // The heart beat for various timeouts + connectionHeartbeatFeature?.OnHeartbeat(state => ((Http3Connection)state).Tick(), this); + + // Register for graceful shutdown of the server + using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((Http3Connection)state).StopProcessingNextRequest(), this); + + // Register for connection close + using var closedRegistration = _context.MultiplexedConnectionContext.ConnectionClosed.Register(state => ((Http3Connection)state).OnConnectionClosed(), this); + + await InnerProcessRequestsAsync(httpApplication); + } + catch (Exception ex) + { + Log.LogCritical(0, ex, $"Unexpected exception in {nameof(Http3Connection)}.{nameof(ProcessRequestsAsync)}."); + } + finally + { + } + } + + // For testing only + internal void Initialize() + { + } + + public void StopProcessingNextRequest() + { + bool previousState; + lock (_protocolSelectionLock) + { + previousState = _aborted; + } + + if (!previousState) + { + //var initiator = serverInitiated ? GracefulCloseInitiator.Server : GracefulCloseInitiator.Client; + + //if (Interlocked.CompareExchange(ref _gracefulCloseInitiator, initiator, GracefulCloseInitiator.None) == GracefulCloseInitiator.None) + //{ + // Input.CancelPendingRead(); + //} + } + } + + public void OnConnectionClosed() + { + bool previousState; + lock (_protocolSelectionLock) + { + previousState = _aborted; + } + + if (!previousState) + { + //TryClose(); + //_frameWriter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient)); + } + } + + //private bool TryClose() + //{ + // if (Interlocked.Exchange(ref _isClosed, 1) == 0) + // { + // Log.Http2ConnectionClosed(_context.ConnectionId, _highestOpenedStreamId); + // return true; + // } + + // return false; + //} + + //public void Abort(ConnectionAbortedException ex) + //{ + // if (TryClose()) + // { + // _frameWriter.WriteGoAwayAsync(int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + // } + + // _frameWriter.Abort(ex); + //} + + public void Abort(ConnectionAbortedException ex) + { + bool previousState; + + lock (_protocolSelectionLock) + { + previousState = _aborted; + _aborted = true; + } + + if (!previousState) + { + InnerAbort(ex); + } + } + + + public void Tick() + { + if (_aborted) + { + // It's safe to check for timeouts on a dead connection, + // but try not to in order to avoid extraneous logs. + return; + } + + // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat. + var now = _systemClock.UtcNowUnsynchronized; + _timeoutControl.Tick(now); + } + + public void OnTimeout(TimeoutReason reason) + { + // In the cases that don't log directly here, we expect the setter of the timeout to also be the input + // reader, so when the read is canceled or aborted, the reader should write the appropriate log. + switch (reason) + { + case TimeoutReason.KeepAlive: + StopProcessingNextRequest(); + break; + case TimeoutReason.RequestHeaders: + HandleRequestHeadersTimeout(); + break; + case TimeoutReason.ReadDataRate: + HandleReadDataRateTimeout(); + break; + case TimeoutReason.WriteDataRate: + Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, "" /*TraceIdentifier*/); // TODO trace identifier. + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)); + break; + case TimeoutReason.RequestBodyDrain: + case TimeoutReason.TimeoutFeature: + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer)); + break; + default: + Debug.Assert(false, "Invalid TimeoutReason"); + break; + } + } + + private async Task InnerProcessRequestsAsync(IHttpApplication application) { // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); @@ -71,23 +241,24 @@ public async Task ProcessRequestsAsync(IHttpApplication appl break; } - var httpConnectionContext = new HttpConnectionContext + var streamDirectionFeature = streamContext.Features.Get(); + + Debug.Assert(streamDirectionFeature != null); + + var httpConnectionContext = new Http3StreamContext { - ConnectionId = streamContext.ConnectionId, - ConnectionContext = streamContext, - Protocols = HttpProtocols.Http3, - ServiceContext = Context.ServiceContext, + ConnectionId = streamContext.StreamId.ToString(), + StreamContext = streamContext, + ServiceContext = _context.ServiceContext, ConnectionFeatures = streamContext.Features, - MemoryPool = Context.MemoryPool, + MemoryPool = _context.MemoryPool, Transport = streamContext.Transport, - TimeoutControl = Context.TimeoutControl, + TimeoutControl = _context.TimeoutControl, LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint }; - - - if (streamContext.Direction == Direction.UnidirectionalInbound) + if (!streamDirectionFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream(application, this, httpConnectionContext); @@ -148,28 +319,24 @@ private async ValueTask CreateDecoderStream(IHttpApplication private async ValueTask CreateNewUnidirectionalStreamAsync(IHttpApplication application) { - var connectionContext = await _multiplexedContext.ConnectAsync(unidirectional: true); - var httpConnectionContext = new HttpConnectionContext + var streamContext = await _multiplexedContext.ConnectAsync(); + var httpConnectionContext = new Http3StreamContext { //ConnectionId = "", TODO getting stream ID from stream that isn't started throws an exception. - ConnectionContext = connectionContext, + StreamContext = streamContext, Protocols = HttpProtocols.Http3, - ServiceContext = Context.ServiceContext, - ConnectionFeatures = connectionContext.Features, - MemoryPool = Context.MemoryPool, - Transport = connectionContext.Transport, - TimeoutControl = Context.TimeoutControl, - LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint, - RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint + ServiceContext = _context.ServiceContext, + ConnectionFeatures = streamContext.Features, + MemoryPool = _context.MemoryPool, + Transport = streamContext.Transport, + TimeoutControl = _context.TimeoutControl, + LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, + RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint }; return new Http3ControlStream(application, this, httpConnectionContext); } - public void StopProcessingNextRequest() - { - } - public void HandleRequestHeadersTimeout() { } @@ -186,7 +353,7 @@ public void Tick(DateTimeOffset now) { } - public void Abort(ConnectionAbortedException ex) + private void InnerAbort(ConnectionAbortedException ex) { lock (_sync) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 087013a6ccf7..ff2b83307609 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -25,14 +25,14 @@ internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThread private Http3OutputProducer _http3Output; private int _isClosed; private int _gracefulCloseInitiator; - private readonly HttpConnectionContext _context; + private readonly Http3StreamContext _context; private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); private readonly Http3Connection _http3Connection; private bool _receivedHeaders; public Pipe RequestBodyPipe { get; } - public Http3Stream(Http3Connection http3Connection, HttpConnectionContext context) + public Http3Stream(Http3Connection http3Connection, Http3StreamContext context) { Initialize(context); // First, determine how we know if an Http3stream is unidirectional or bidirectional diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs index 8d8d6a663ffa..4a43f88f56de 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs @@ -10,7 +10,7 @@ class Http3Stream : Http3Stream, IHostContextContainer { private readonly IHttpApplication _application; - public Http3Stream(IHttpApplication application, Http3Connection connection, HttpConnectionContext context) : base(connection, context) + public Http3Stream(IHttpApplication application, Http3Connection connection, Http3StreamContext context) : base(connection, context) { _application = application; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs index 381caf1b2fd1..42bf2cb6c165 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Buffers; -using System.IO.Pipelines; using System.Net; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs deleted file mode 100644 index 89e292ecb190..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionTemp.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal -{ - internal class Http3ConnectionTemp : ITimeoutHandler - { - // Use C#7.3's ReadOnlySpan optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/ - private static ReadOnlySpan Http2Id => new[] { (byte)'h', (byte)'2' }; - - private readonly Http3ConnectionContext _context; - private readonly ISystemClock _systemClock; - private readonly TimeoutControl _timeoutControl; - - private readonly object _protocolSelectionLock = new object(); - private ProtocolSelectionState _protocolSelectionState = ProtocolSelectionState.Initializing; - private IRequestProcessor _requestProcessor; - private Http1Connection _http1Connection; - - public Http3ConnectionTemp(Http3ConnectionContext context) - { - _context = context; - _systemClock = _context.ServiceContext.SystemClock; - - _timeoutControl = new TimeoutControl(this); - - // Tests override the timeout control sometimes - _context.TimeoutControl ??= _timeoutControl; - } - - private IKestrelTrace Log => _context.ServiceContext.Log; - - public async Task ProcessRequestsAsync(IHttpApplication httpApplication) - { - try - { - // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. - _timeoutControl.Initialize(_systemClock.UtcNowTicks); - - IRequestProcessor requestProcessor = null; - - requestProcessor = new Http3Connection(_context); - _protocolSelectionState = ProtocolSelectionState.Selected; - - _requestProcessor = requestProcessor; - - if (requestProcessor != null) - { - var connectionHeartbeatFeature = _context.ConnectionFeatures.Get(); - var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get(); - - // These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel, - // we'll need to handle these missing. - Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!"); - Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!"); - - // Register the various callbacks once we're going to start processing requests - - // The heart beat for various timeouts - connectionHeartbeatFeature?.OnHeartbeat(state => ((Http3ConnectionTemp)state).Tick(), this); - - // Register for graceful shutdown of the server - using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((Http3ConnectionTemp)state).StopProcessingNextRequest(), this); - - // Register for connection close - using var closedRegistration = _context.MultiplexedConnectionContext.ConnectionClosed.Register(state => ((Http3ConnectionTemp)state).OnConnectionClosed(), this); - - await requestProcessor.ProcessRequestsAsync(httpApplication); - } - } - catch (Exception ex) - { - Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}."); - } - finally - { - if (_http1Connection?.IsUpgraded == true) - { - _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); - } - } - } - - // For testing only - internal void Initialize(IRequestProcessor requestProcessor) - { - _requestProcessor = requestProcessor; - _http1Connection = requestProcessor as Http1Connection; - _protocolSelectionState = ProtocolSelectionState.Selected; - } - - private void StopProcessingNextRequest() - { - ProtocolSelectionState previousState; - lock (_protocolSelectionLock) - { - previousState = _protocolSelectionState; - Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing"); - - switch (_protocolSelectionState) - { - case ProtocolSelectionState.Selected: - case ProtocolSelectionState.Aborted: - break; - } - } - - switch (previousState) - { - case ProtocolSelectionState.Selected: - _requestProcessor.StopProcessingNextRequest(); - break; - case ProtocolSelectionState.Aborted: - break; - } - } - - private void OnConnectionClosed() - { - ProtocolSelectionState previousState; - lock (_protocolSelectionLock) - { - previousState = _protocolSelectionState; - Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing"); - - switch (_protocolSelectionState) - { - case ProtocolSelectionState.Selected: - case ProtocolSelectionState.Aborted: - break; - } - } - - switch (previousState) - { - case ProtocolSelectionState.Selected: - _requestProcessor.OnInputOrOutputCompleted(); - break; - case ProtocolSelectionState.Aborted: - break; - } - } - - private void Abort(ConnectionAbortedException ex) - { - ProtocolSelectionState previousState; - - lock (_protocolSelectionLock) - { - previousState = _protocolSelectionState; - Debug.Assert(previousState != ProtocolSelectionState.Initializing, "The state should never be initializing"); - - _protocolSelectionState = ProtocolSelectionState.Aborted; - } - - switch (previousState) - { - case ProtocolSelectionState.Selected: - _requestProcessor.Abort(ex); - break; - case ProtocolSelectionState.Aborted: - break; - } - } - - - private void Tick() - { - if (_protocolSelectionState == ProtocolSelectionState.Aborted) - { - // It's safe to check for timeouts on a dead connection, - // but try not to in order to avoid extraneous logs. - return; - } - - // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat. - var now = _systemClock.UtcNowUnsynchronized; - _timeoutControl.Tick(now); - _requestProcessor.Tick(now); - } - - public void OnTimeout(TimeoutReason reason) - { - // In the cases that don't log directly here, we expect the setter of the timeout to also be the input - // reader, so when the read is canceled or aborted, the reader should write the appropriate log. - switch (reason) - { - case TimeoutReason.KeepAlive: - _requestProcessor.StopProcessingNextRequest(); - break; - case TimeoutReason.RequestHeaders: - _requestProcessor.HandleRequestHeadersTimeout(); - break; - case TimeoutReason.ReadDataRate: - _requestProcessor.HandleReadDataRateTimeout(); - break; - case TimeoutReason.WriteDataRate: - Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection?.TraceIdentifier); - Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)); - break; - case TimeoutReason.RequestBodyDrain: - case TimeoutReason.TimeoutFeature: - Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer)); - break; - default: - Debug.Assert(false, "Invalid TimeoutReason"); - break; - } - } - - private enum ProtocolSelectionState - { - Initializing, - Selected, - Aborted - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs b/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs new file mode 100644 index 000000000000..0e99eb88de19 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.IO.Pipelines; +using System.Net; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class Http3StreamContext : HttpConnectionContext + { + public StreamContext StreamContext { get; set; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs index fdabf48247f6..52c76a840184 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs @@ -38,6 +38,19 @@ public void ConnectionStart(ConnectionContext connection) } } + [NonEvent] + public void ConnectionStart(MultiplexedConnectionContext connection) + { + // avoid allocating strings unless this event source is enabled + if (IsEnabled()) + { + ConnectionStart( + connection.ConnectionId, + connection.LocalEndPoint?.ToString(), + connection.RemoteEndPoint?.ToString()); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] [Event(1, Level = EventLevel.Verbose)] private void ConnectionStart(string connectionId, @@ -61,6 +74,15 @@ public void ConnectionStop(ConnectionContext connection) } } + [NonEvent] + public void ConnectionStop(MultiplexedConnectionContext connection) + { + if (IsEnabled()) + { + ConnectionStop(connection.ConnectionId); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] [Event(2, Level = EventLevel.Verbose)] private void ConnectionStop(string connectionId) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs new file mode 100644 index 000000000000..5c981e39caeb --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs @@ -0,0 +1,110 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal class MultiplexedConnectionManager + { + private readonly ConcurrentDictionary _connectionReferences = new ConcurrentDictionary(); + private readonly IKestrelTrace _trace; + + public MultiplexedConnectionManager(IKestrelTrace trace) + { + _trace = trace; + } + + public void AddConnection(long id, MultiplexedKestrelConnection connection) + { + if (!_connectionReferences.TryAdd(id, new MultiplexedConnectionReference(connection))) + { + throw new ArgumentException(nameof(id)); + } + } + + public void RemoveConnection(long id) + { + if (!_connectionReferences.TryRemove(id, out var reference)) + { + throw new ArgumentException(nameof(id)); + } + + if (reference.TryGetConnection(out var connection)) + { + connection.Complete(); + } + } + + public void Walk(Action callback) + { + foreach (var kvp in _connectionReferences) + { + var reference = kvp.Value; + + if (reference.TryGetConnection(out var connection)) + { + callback(connection); + } + else if (_connectionReferences.TryRemove(kvp.Key, out reference)) + { + // It's safe to modify the ConcurrentDictionary in the foreach. + // The connection reference has become unrooted because the application never completed. + _trace.ApplicationNeverCompleted(reference.ConnectionId); + } + + // If both conditions are false, the connection was removed during the heartbeat. + } + } + + public async Task CloseAllConnectionsAsync(CancellationToken token) + { + var closeTasks = new List(); + + Walk(connection => + { + connection.RequestClose(); + closeTasks.Add(connection.ExecutionTask); + }); + + var allClosedTask = Task.WhenAll(closeTasks.ToArray()); + return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; + } + + public async Task AbortAllConnectionsAsync() + { + var abortTasks = new List(); + + Walk(connection => + { + connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); + abortTasks.Add(connection.ExecutionTask); + }); + + var allAbortedTask = Task.WhenAll(abortTasks.ToArray()); + return await Task.WhenAny(allAbortedTask, Task.Delay(1000)).ConfigureAwait(false) == allAbortedTask; + } + + private static Task CancellationTokenAsTask(CancellationToken token) + { + if (token.IsCancellationRequested) + { + return Task.CompletedTask; + } + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + token.Register(() => tcs.SetResult(null)); + return tcs.Task; + } + + private static ResourceCounter GetCounter(long? number) + => number.HasValue + ? ResourceCounter.Quota(number.Value) + : ResourceCounter.Unlimited; + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs new file mode 100644 index 000000000000..92c1782911df --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal class MultiplexedConnectionReference + { + private readonly WeakReference _weakReference; + + public MultiplexedConnectionReference(MultiplexedKestrelConnection connection) + { + _weakReference = new WeakReference(connection); + ConnectionId = connection.TransportConnection.ConnectionId; + } + + public string ConnectionId { get; } + + public bool TryGetConnection(out MultiplexedKestrelConnection connection) + { + return _weakReference.TryGetTarget(out connection); + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs new file mode 100644 index 000000000000..cfdd55108665 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs @@ -0,0 +1,230 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal class MultiplexedKestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature, IThreadPoolWorkItem + { + private List<(Action handler, object state)> _heartbeatHandlers; + private readonly object _heartbeatLock = new object(); + + private Stack, object>> _onCompleted; + private bool _completed; + + private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); + private readonly TaskCompletionSource _completionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly long _id; + private readonly ServiceContext _serviceContext; + private readonly MultiplexedConnectionDelegate _multiplexedConnectionDelegate; + + public MultiplexedKestrelConnection(long id, + ServiceContext serviceContext, + MultiplexedConnectionDelegate connectionDelegate, + MultiplexedConnectionContext connectionContext, + IKestrelTrace logger) + { + _id = id; + _serviceContext = serviceContext; + _multiplexedConnectionDelegate = connectionDelegate; + Logger = logger; + TransportConnection = connectionContext; + + connectionContext.Features.Set(this); + connectionContext.Features.Set(this); + connectionContext.Features.Set(this); + ConnectionClosedRequested = _connectionClosingCts.Token; + } + + private IKestrelTrace Logger { get; } + + public MultiplexedConnectionContext TransportConnection { get; set; } + public CancellationToken ConnectionClosedRequested { get; set; } + public Task ExecutionTask => _completionTcs.Task; + + public void TickHeartbeat() + { + lock (_heartbeatLock) + { + if (_heartbeatHandlers == null) + { + return; + } + + foreach (var (handler, state) in _heartbeatHandlers) + { + handler(state); + } + } + } + + public void OnHeartbeat(Action action, object state) + { + lock (_heartbeatLock) + { + if (_heartbeatHandlers == null) + { + _heartbeatHandlers = new List<(Action handler, object state)>(); + } + + _heartbeatHandlers.Add((action, state)); + } + } + + void IConnectionCompleteFeature.OnCompleted(Func callback, object state) + { + if (_completed) + { + throw new InvalidOperationException("The connection is already complete."); + } + + if (_onCompleted == null) + { + _onCompleted = new Stack, object>>(); + } + _onCompleted.Push(new KeyValuePair, object>(callback, state)); + } + + public Task FireOnCompletedAsync() + { + if (_completed) + { + throw new InvalidOperationException("The connection is already complete."); + } + + _completed = true; + var onCompleted = _onCompleted; + + if (onCompleted == null || onCompleted.Count == 0) + { + return Task.CompletedTask; + } + + return CompleteAsyncMayAwait(onCompleted); + } + + private Task CompleteAsyncMayAwait(Stack, object>> onCompleted) + { + while (onCompleted.TryPop(out var entry)) + { + try + { + var task = entry.Key.Invoke(entry.Value); + if (!task.IsCompletedSuccessfully) + { + return CompleteAsyncAwaited(task, onCompleted); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); + } + } + + return Task.CompletedTask; + } + + private async Task CompleteAsyncAwaited(Task currentTask, Stack, object>> onCompleted) + { + try + { + await currentTask; + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); + } + + while (onCompleted.TryPop(out var entry)) + { + try + { + await entry.Key.Invoke(entry.Value); + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); + } + } + } + + public void RequestClose() + { + try + { + _connectionClosingCts.Cancel(); + } + catch (ObjectDisposedException) + { + // There's a race where the token could be disposed + // swallow the exception and no-op + } + } + + public void Complete() + { + _completionTcs.TrySetResult(null); + + _connectionClosingCts.Dispose(); + } + + void IThreadPoolWorkItem.Execute() + { + _ = ExecuteAsync(); + } + + internal async Task ExecuteAsync() + { + var connectionContext = TransportConnection; + + try + { + Logger.ConnectionStart(connectionContext.ConnectionId); + KestrelEventSource.Log.ConnectionStart(connectionContext); + + using (BeginConnectionScope(connectionContext)) + { + try + { + await _multiplexedConnectionDelegate(connectionContext); + } + catch (Exception ex) + { + Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); + } + } + } + finally + { + await FireOnCompletedAsync(); + + Logger.ConnectionStop(connectionContext.ConnectionId); + KestrelEventSource.Log.ConnectionStop(connectionContext); + + // Dispose the transport connection, this needs to happen before removing it from the + // connection manager so that we only signal completion of this connection after the transport + // is properly torn down. + await TransportConnection.DisposeAsync(); + + _serviceContext.ConnectionManager.RemoveConnection(_id); + } + } + + private IDisposable BeginConnectionScope(MultiplexedConnectionContext connectionContext) + { + if (Logger.IsEnabled(LogLevel.Critical)) + { + return Logger.BeginScope(new ConnectionLogScope(connectionContext.ConnectionId)); + } + + return null; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs index 8fa70811929a..f5c7fcf0413f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs @@ -54,9 +54,9 @@ async Task AcceptConnectionsAsync() // Add the connection to the connection manager before we queue it for execution var id = Interlocked.Increment(ref _lastConnectionId); // TODO Don't pass null in here! use a base class - var kestrelConnection = new KestrelConnection(id, _serviceContext, null, null, Log); + var kestrelConnection = new MultiplexedKestrelConnection(id, _serviceContext, _connectionDelegate, connection, Log); - _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection); + _serviceContext.MultiplexedConnectionManager.AddConnection(id, kestrelConnection); Log.ConnectionAccepted(connection.ConnectionId); diff --git a/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs b/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs index dae02a6eb070..8f9926ded3eb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs @@ -21,6 +21,8 @@ internal class ServiceContext public ConnectionManager ConnectionManager { get; set; } + public MultiplexedConnectionManager MultiplexedConnectionManager { get; set; } + public Heartbeat Heartbeat { get; set; } public KestrelServerOptions ServerOptions { get; set; } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 7ff4e001fc8a..b1472f71182a 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -57,7 +57,7 @@ internal KestrelServer(IEnumerable transportFactorie _transportFactories = transportFactories.ToList(); _multiplexedTransportFactories = multiplexedFactories?.ToList(); - if (_transportFactories.Count == 0) + if (_transportFactories.Count == 0 && _multiplexedTransportFactories?.Count == 0) { throw new InvalidOperationException(CoreStrings.TransportNotFound); } @@ -89,6 +89,9 @@ private static ServiceContext CreateServiceContext(IOptions= HttpProtocols.Http3) - { - foreach (var transportFactory in _transportFactories) - { - if (transportFactory is IMultiplexedConnectionListenerFactory) - { - // Don't break early. Always use the last registered factory. - factory = transportFactory; - } - } + var acceptLoopTask = multiplexedConnectionDispatcher.StartAcceptingConnections(multiplexedTransport); + _multiplexedTransports.Add((multiplexedTransport, acceptLoopTask)); - if (factory == null) - { - throw new InvalidOperationException(CoreStrings.QuicTransportNotFound); - } - } - else - { - foreach (var transportFactory in _transportFactories) - { - if (!(transportFactory is IMultiplexedConnectionListenerFactory)) - { - factory = transportFactory; - } - } + options.EndPoint = multiplexedTransport.EndPoint; } - - var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false); - - var multiplexedFactory = _multiplexedTransportFactories.Last(); - var multiplexedTransport = await multiplexedFactory.BindAsync(options.EndPoint).ConfigureAwait(false); - - // Update the endpoint - // TODO I don't know if it makes sense to have two factories - options.EndPoint = transport.EndPoint; - var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); - - _transports.Add((transport, acceptLoopTask)); } await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false); diff --git a/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs index 1b18ddf5c8ee..ec5e9b000392 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { @@ -35,7 +36,7 @@ public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint }; - var connection = new Http3ConnectionTemp(http3ConnectionContext); + var connection = new Http3Connection(http3ConnectionContext); return connection.ProcessRequestsAsync(_application); } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index 34a7e299bcb4..74494e5ab93c 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -3,16 +3,16 @@ using System; using System.Net.Quic; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Abstractions.Features; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal class QuicConnectionContext : TransportConnection, IQuicStreamListenerFeature, IQuicCreateStreamFeature + internal class QuicConnectionContext : TransportMultiplexedConnection { private QuicConnection _connection; private readonly QuicTransportContext _context; @@ -26,22 +26,20 @@ public QuicConnectionContext(QuicConnection connection, QuicTransportContext con _context = context; _connection = connection; Features.Set(new FakeTlsConnectionFeature()); - Features.Set(this); - Features.Set(this); _log.NewConnection(ConnectionId); } - public ValueTask StartUnidirectionalStreamAsync() + public ValueTask StartUnidirectionalStreamAsync() { var stream = _connection.OpenUnidirectionalStream(); - return new ValueTask(new QuicStreamContext(stream, this, _context)); + return new ValueTask(new QuicStreamContext(stream, this, _context)); } - public ValueTask StartBidirectionalStreamAsync() + public ValueTask StartBidirectionalStreamAsync() { var stream = _connection.OpenBidirectionalStream(); - return new ValueTask(new QuicStreamContext(stream, this, _context)); + return new ValueTask(new QuicStreamContext(stream, this, _context)); } public override async ValueTask DisposeAsync() @@ -71,22 +69,34 @@ public override void Abort(ConnectionAbortedException abortReason) _closeTask = _connection.CloseAsync(errorCode: _context.Options.AbortErrorCode); } - public async ValueTask AcceptAsync() + public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - var stream = await _connection.AcceptStreamAsync(); - try + var stream = await _connection.AcceptStreamAsync(cancellationToken); + return new QuicStreamContext(stream, this, _context); + } + + public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) + { + QuicStream quicStream; + + if (features != null) { - // Because the stream is wrapped with a quic connection provider, - // we need to check a property to check if this is null - // Will be removed once the provider abstraction is removed. - _ = stream.CanRead; + var streamDirectionFeature = features.Get(); + if (streamDirectionFeature.CanRead) + { + quicStream = _connection.OpenBidirectionalStream(); + } + else + { + quicStream = _connection.OpenUnidirectionalStream(); + } } - catch (Exception) + else { - return null; + quicStream = _connection.OpenBidirectionalStream(); } - return new QuicStreamContext(stream, this, _context); + return new ValueTask(new QuicStreamContext(quicStream, this, _context)); } } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs index efbca9b1cb06..5a0df2aa2e83 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal /// /// Listens for new Quic Connections. /// - internal class QuicConnectionListener : IConnectionListener, IAsyncDisposable + internal class QuicConnectionListener : IMultiplexedConnectionListener, IAsyncDisposable { private readonly IQuicTrace _log; private bool _disposed; @@ -36,21 +36,9 @@ public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndP public EndPoint EndPoint { get; set; } - public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); - try - { - // Because the stream is wrapped with a quic connection provider, - // we need to check a property to check if this is null - // Will be removed once the provider abstraction is removed. - _ = quicConnection.LocalEndPoint; - } - catch (Exception) - { - return null; - } - return new QuicConnectionContext(quicConnection, _context); } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs index 6097e78e834f..1c6e36f0f8c0 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal class QuicStreamContext : TransportConnection, IQuicStreamFeature + internal class QuicStreamContext : TransportStream, IStreamDirectionFeature { private readonly Task _processingTask; private readonly QuicStream _stream; @@ -46,7 +46,7 @@ public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, Qu var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); - Features.Set(this); + Features.Set(this); // TODO populate the ITlsConnectionFeature (requires client certs). Features.Set(new FakeTlsConnectionFeature()); @@ -66,7 +66,7 @@ public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, Qu public bool CanRead { get; } public bool CanWrite { get; } - public long StreamId + public override long StreamId { get { @@ -90,6 +90,7 @@ public override string ConnectionId } } + private async Task StartAsync() { try diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs index d2e7c1bcb337..bb67ac4e8049 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs @@ -9,13 +9,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic { - public class QuicConnectionFactory : IConnectionFactory + public class QuicConnectionFactory : IMultiplexedConnectionFactory { private QuicTransportContext _transportContext; @@ -32,7 +33,7 @@ public QuicConnectionFactory(IOptions options, ILoggerFact _transportContext = new QuicTransportContext(trace, options.Value); } - public async ValueTask ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default) + public async ValueTask ConnectAsync(EndPoint endPoint, IFeatureCollection features = null, CancellationToken cancellationToken = default) { if (!(endPoint is IPEndPoint ipEndPoint)) { diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs index 5962df595e65..64f59b4e16a4 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs @@ -6,8 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -35,10 +35,10 @@ public QuicTransportFactory(ILoggerFactory loggerFactory, IOptions BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) + public ValueTask BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default) { var transport = new QuicConnectionListener(_options, _log, endpoint); - return new ValueTask(transport); + return new ValueTask(transport); } } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs index 09cfbb73b7a3..758005d7f256 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs @@ -24,11 +24,6 @@ public class QuicTransportOptions /// public string Alpn { get; set; } - /// - /// The registration name to use in Quic. - /// - public string RegistrationName { get; set; } - /// /// The certificate that MsQuic will use. /// diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index f989d6260752..4e5be08e45c5 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Net; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Connections; @@ -15,47 +14,38 @@ public class Program { public static void Main(string[] args) { - var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true); + var cert = CertificateLoader.LoadFromStoreCert("JUSTIN-LAPTOP", StoreName.My.ToString(), StoreLocation.LocalMachine, true); var hostBuilder = new HostBuilder() - .ConfigureLogging((_, factory) => - { - factory.SetMinimumLevel(LogLevel.Trace); - factory.AddConsole(); - }) - .ConfigureWebHost(webHost => - { - webHost.UseKestrel() - // Things like APLN and cert should be able to be passed from corefx into bedrock - .UseQuic(options => - { - options.Certificate = cert; - options.RegistrationName = "Quic"; - options.Alpn = "h3-25"; - options.IdleTimeout = TimeSpan.FromHours(1); - }) - .ConfigureKestrel((context, options) => - { - var basePort = 443; - options.EnableAltSvc = true; - options.Listen(IPAddress.Any, basePort, listenOptions => - { - listenOptions.UseHttps(httpsOptions => - { - httpsOptions.ServerCertificate = cert; - }); - }); - options.Listen(IPAddress.Any, basePort, listenOptions => - { + .ConfigureLogging((_, factory) => + { + factory.SetMinimumLevel(LogLevel.Trace); + factory.AddConsole(); + }) + .ConfigureWebHost(webHost => + { + webHost.UseKestrel() + .UseQuic(options => + { + options.Certificate = cert; // Shouldn't need this either here. + options.Alpn = "h3-25"; // Shouldn't need to populate this as well. + options.IdleTimeout = TimeSpan.FromHours(1); + }) + .ConfigureKestrel((context, options) => + { + var basePort = 4444; + options.EnableAltSvc = true; + options.Listen(IPAddress.Any, basePort, listenOptions => + { listenOptions.UseHttps(httpsOptions => - { - httpsOptions.ServerCertificate = cert; - }); - listenOptions.Protocols = HttpProtocols.Http3; - }); - }) - .UseStartup(); - }); + { + httpsOptions.ServerCertificate = cert; + }); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + }); + }) + .UseStartup(); + }); hostBuilder.Build().Run(); } diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index 96d680e5e2ac..d9def3ddc2b3 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -1,13 +1,11 @@ using System; using System.Buffers; using System.Net; -using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Logging; namespace QuicSampleApp @@ -53,24 +51,18 @@ async Task EchoServer(MultiplexedConnectionContext connection) while (true) { var stream = await connection.AcceptAsync(); - try + while (true) { - while (true) - { - var result = await stream.Transport.Input.ReadAsync(); + var result = await stream.Transport.Input.ReadAsync(); - if (result.IsCompleted) - { - break; - } + if (result.IsCompleted) + { + break; + } - await stream.Transport.Output.WriteAsync(result.Buffer.ToArray()); + await stream.Transport.Output.WriteAsync(result.Buffer.ToArray()); - stream.Transport.Input.AdvanceTo(result.Buffer.End); - } - } - catch (OperationCanceledException) - { + stream.Transport.Input.AdvanceTo(result.Buffer.End); } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index 63dcd5e062f3..ae5c01f3a99b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -10,6 +10,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; @@ -106,8 +107,7 @@ protected void CreateConnection() }; _connection = new Http3Connection(httpConnectionContext); - var httpConnection = new Http3ConnectionTemp(httpConnectionContext); - httpConnection.Initialize(_connection); + var httpConnection = new Http3Connection(httpConnectionContext); _mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny())) .Callback(r => httpConnection.OnTimeout(r)); } @@ -149,7 +149,7 @@ private static long GetOutputResponseBufferSize(ServiceContext serviceContext) internal async ValueTask CreateControlStream(int id) { - var stream = new Http3ControlStream(this, _connection); + var stream = new Http3ControlStream(this); _multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext); await stream.WriteStreamIdAsync(id); return stream; @@ -212,7 +212,7 @@ public Http3RequestStream(Http3TestBase testBase, Http3Connection connection) _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - StreamContext = new TestStreamContext(Direction.BidirectionalInbound); + StreamContext = new TestStreamContext(canRead: true, canWrite: true); StreamContext.Transport = _pair.Transport; } @@ -350,7 +350,7 @@ public Http3ControlStream(Http3TestBase testBase) _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - StreamContext = new TestStreamContext(Direction.UnidirectionalInbound); + StreamContext = new TestStreamContext(canRead: false, canWrite: true); StreamContext.Transport = _pair.Transport; } @@ -406,7 +406,7 @@ public override async ValueTask AcceptAsync(CancellationToken can return null; } - public override ValueTask ConnectAsync(IFeatureCollection features = null, bool unidirectional = false, CancellationToken cancellationToken = default) + public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) { var stream = new Http3ControlStream(_testBase); // TODO put these somewhere to be read. @@ -416,14 +416,13 @@ public override ValueTask ConnectAsync(IFeatureCollection feature private class TestStreamContext : StreamContext { - public TestStreamContext(Direction direction) + public TestStreamContext(bool canRead, bool canWrite) { - Direction = direction; + Features = new FeatureCollection(); + Features.Set(new TestStreamDirectionFeature(canRead, canWrite)); } - public override long StreamId { get; set; } - - public override Direction Direction { get; } + public override long StreamId { get; } public override string ConnectionId { get; set; } @@ -432,6 +431,19 @@ public TestStreamContext(Direction direction) public override IDictionary Items { get; set; } public override IDuplexPipe Transport { get; set; } + + private class TestStreamDirectionFeature : IStreamDirectionFeature + { + public TestStreamDirectionFeature(bool canRead, bool canWrite) + { + CanRead = canRead; + CanWrite = canWrite; + } + + public bool CanRead { get; } + + public bool CanWrite { get; } + } } } } From 152f794b0abd2483bea897b6137301a97861d1c0 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 13 Feb 2020 15:47:48 -0800 Subject: [PATCH 07/27] Trying to figure out diff --- .../Kestrel/Core/src/Internal/Http3/Http3Connection.cs | 6 +++--- .../Kestrel/Core/src/Internal/Http3/Http3Stream.cs | 8 ++++++-- src/Servers/Kestrel/samples/Http3SampleApp/Program.cs | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 3f2839032457..eb461a223525 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -285,9 +285,9 @@ private async Task InnerProcessRequestsAsync(IHttpApplication(IHttpApplication appli { var streamError = error as ConnectionAbortedException ?? new ConnectionAbortedException("The stream has completed.", error); + + //await _appTask; try { - _frameWriter.Complete(); + //_frameWriter.Complete(); } catch { @@ -232,7 +236,7 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli _receivedHeaders = true; - Task.Run(() => base.ProcessRequestsAsync(application)); + _appTask = Task.Run(() => base.ProcessRequestsAsync(application)); return Task.CompletedTask; } diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index 4e5be08e45c5..f3b584b1025e 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -14,7 +14,7 @@ public class Program { public static void Main(string[] args) { - var cert = CertificateLoader.LoadFromStoreCert("JUSTIN-LAPTOP", StoreName.My.ToString(), StoreLocation.LocalMachine, true); + var cert = CertificateLoader.LoadFromStoreCert("JUSTIN-LAPTOP", StoreName.My.ToString(), StoreLocation.CurrentUser, false); var hostBuilder = new HostBuilder() .ConfigureLogging((_, factory) => @@ -33,7 +33,7 @@ public static void Main(string[] args) }) .ConfigureKestrel((context, options) => { - var basePort = 4444; + var basePort = 5557; options.EnableAltSvc = true; options.Listen(IPAddress.Any, basePort, listenOptions => { From 75b56e40bbb5ff488d32ee18797446a34ec2dfd2 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 13 Feb 2020 17:13:52 -0800 Subject: [PATCH 08/27] Functional, time to test --- .../Http3/DefaultStreamDirectionFeature.cs | 20 +++++++++++++++++++ .../src/Internal/Http3/Http3Connection.cs | 8 +++++--- .../src/Internal/QuicConnectionContext.cs | 8 ++++++++ .../Kestrel/samples/Http3SampleApp/Program.cs | 3 ++- 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/Servers/Kestrel/Core/src/Internal/Http3/DefaultStreamDirectionFeature.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/DefaultStreamDirectionFeature.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/DefaultStreamDirectionFeature.cs new file mode 100644 index 000000000000..b96554c171c8 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/DefaultStreamDirectionFeature.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Connections.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 +{ + internal class DefaultStreamDirectionFeature : IStreamDirectionFeature + { + public DefaultStreamDirectionFeature(bool canRead, bool canWrite) + { + CanRead = canRead; + CanWrite = canWrite; + } + + public bool CanRead { get; } + + public bool CanWrite { get; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index eb461a223525..89cf4d90d47a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; @@ -180,7 +181,6 @@ public void Abort(ConnectionAbortedException ex) } } - public void Tick() { if (_aborted) @@ -236,7 +236,7 @@ private async Task InnerProcessRequestsAsync(IHttpApplication(IHttpApplication private async ValueTask CreateNewUnidirectionalStreamAsync(IHttpApplication application) { - var streamContext = await _multiplexedContext.ConnectAsync(); + var features = new FeatureCollection(); + features.Set(new DefaultStreamDirectionFeature(canRead: false, canWrite: true)); + var streamContext = await _multiplexedContext.ConnectAsync(features); var httpConnectionContext = new Http3StreamContext { //ConnectionId = "", TODO getting stream ID from stream that isn't started throws an exception. diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index 74494e5ab93c..4d36d058868a 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -72,6 +72,14 @@ public override void Abort(ConnectionAbortedException abortReason) public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { var stream = await _connection.AcceptStreamAsync(cancellationToken); + try + { + _ = stream.CanRead; + } + catch (Exception) + { + return null; + } return new QuicStreamContext(stream, this, _context); } diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index f3b584b1025e..082c1a370fc8 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -41,7 +41,8 @@ public static void Main(string[] args) { httpsOptions.ServerCertificate = cert; }); - listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + listenOptions.Protocols = HttpProtocols.Http3; + //listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); }) .UseStartup(); From af47eaf06c632c6b3ec2147f8c1772b7f2a1273a Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 13 Feb 2020 21:30:09 -0800 Subject: [PATCH 09/27] Don't modify prior API --- .../Connections.Abstractions/src/IConnectionFactory.cs | 3 +-- .../Kestrel/Core/src/Internal/Http3/Http3Connection.cs | 2 -- .../Transport.Sockets/src/Client/SocketConnectionFactory.cs | 2 +- src/Servers/Kestrel/samples/Http3SampleApp/Program.cs | 4 ++-- src/Servers/Kestrel/samples/QuicSampleApp/Program.cs | 1 - src/Servers/Kestrel/samples/QuicSampleClient/Program.cs | 3 --- 6 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs index a2ac35c67ca2..330844b11280 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs @@ -18,11 +18,10 @@ public interface IConnectionFactory /// Creates a new connection to an endpoint. /// /// The to connect to. - /// A feature collection to pass options when connecting. /// The token to monitor for cancellation requests. The default value is . /// /// A that represents the asynchronous connect, yielding the for the new connection when completed. /// - ValueTask ConnectAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default); + ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 89cf4d90d47a..16a04e1cb7da 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -6,8 +6,6 @@ using System.Diagnostics; using System.Net; using System.Net.Http; -using System.Runtime.InteropServices.ComTypes; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs index 4f3449c0370e..dc860eb8f548 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs @@ -40,7 +40,7 @@ public SocketConnectionFactory(IOptions options, ILogger _trace = new SocketsTrace(logger); } - public async ValueTask ConnectAsync(EndPoint endpoint, IFeatureCollection collection = null, CancellationToken cancellationToken = default) + public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var ipEndPoint = endpoint as IPEndPoint; diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index 082c1a370fc8..3847204a06c1 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -41,8 +41,8 @@ public static void Main(string[] args) { httpsOptions.ServerCertificate = cert; }); - listenOptions.Protocols = HttpProtocols.Http3; - //listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + //listenOptions.Protocols = HttpProtocols.Http3; + listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); }) .UseStartup(); diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index d9def3ddc2b3..f267bb8c510f 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -32,7 +32,6 @@ public static void Main(string[] args) .UseQuic(options => { options.Certificate = null; - options.RegistrationName = "AspNetCore-MsQuic"; options.Alpn = "QuicTest"; options.IdleTimeout = TimeSpan.FromHours(1); }) diff --git a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs index 050d346e2e40..082d95cbfa56 100644 --- a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs @@ -1,10 +1,8 @@ using System; using System.Buffers; using System.Net; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Connections; @@ -31,7 +29,6 @@ static async Task Main(string[] args) services.Configure((options) => { options.Alpn = "QuicTest"; - options.RegistrationName = "Quic-AspNetCore-client"; options.Certificate = null; options.IdleTimeout = TimeSpan.FromHours(1); }); From 7668395e1fc4a2e9b174a03c7fcb8c1758e1fe0e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 14 Feb 2020 00:33:03 -0800 Subject: [PATCH 10/27] Got a test working --- .../src/ConnectionBuilderExtensions.cs | 2 +- .../Features/IConnectionTransportFeature.cs | 2 +- ...mFeature.cs => IStreamDirectionFeature.cs} | 0 .../src/IConnectionFactory.cs | 2 - .../src/IConnectionListenerFactory.cs | 1 - .../src/MultiplexedConnectionContext.cs | 39 +++++++++------ src/Servers/Kestrel/Core/src/CoreStrings.resx | 12 +++++ .../src/Internal/Http3/Http3Connection.cs | 4 +- .../Core/src/Internal/Http3/Http3Stream.cs | 18 +++---- .../MultiplexedHeartbeatManager.cs | 41 +++++++++++++++ src/Servers/Kestrel/Core/src/KestrelServer.cs | 4 +- .../test/MultplexedConnectionContextTests.cs | 27 ++++++++++ .../Http3/Http3StreamTests.cs | 17 +++++++ .../Http3/Http3TestBase.cs | 50 +++++++++++-------- 14 files changed, 167 insertions(+), 52 deletions(-) rename src/Servers/Connections.Abstractions/src/Features/{IQuicStreamFeature.cs => IStreamDirectionFeature.cs} (100%) create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs create mode 100644 src/Servers/Kestrel/Core/test/MultplexedConnectionContextTests.cs diff --git a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs index 55b0311eb977..100917b0094a 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs @@ -40,4 +40,4 @@ public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder, }); } } -} +} \ No newline at end of file diff --git a/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs index deba5a8c1efd..0b218972d70c 100644 --- a/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs +++ b/src/Servers/Connections.Abstractions/src/Features/IConnectionTransportFeature.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO.Pipelines; diff --git a/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IStreamDirectionFeature.cs similarity index 100% rename from src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs rename to src/Servers/Connections.Abstractions/src/Features/IStreamDirectionFeature.cs diff --git a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs index 330844b11280..1b70c54eabef 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionFactory.cs @@ -1,11 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs index dd8843f7d3f0..2bcfa5ebca84 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListenerFactory.cs @@ -4,7 +4,6 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs index 35c6eef506bf..078b179b3bb9 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -43,6 +43,30 @@ public abstract class MultiplexedConnectionContext : IAsyncDisposable /// public virtual EndPoint RemoteEndPoint { get; set; } + /// + /// Releases resources for the underlying connection. + /// + /// A that completes when resources have been released. + public virtual ValueTask DisposeAsync() + { + return default; + } + + /// + /// Asynchronously accept an incoming stream on the connection. + /// + /// + /// + public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); + + /// + /// Creates an outbound connection + /// + /// + /// + /// + public abstract ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); + /// /// Aborts the underlying connection. /// @@ -58,19 +82,6 @@ public virtual void Abort(ConnectionAbortedException abortReason) /// /// Aborts the underlying connection. /// - public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); - - /// - /// Releases resources for the underlying connection. - /// - /// A that completes when resources have been released. - public virtual ValueTask DisposeAsync() - { - return default; - } - - public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); - - public abstract ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); + public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via MultiplexedConnectionContext.Abort().")); } } diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 3a25467bcc2f..4a5d12f26468 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -527,6 +527,18 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l A new stream was refused because this connection has reached its stream limit. + + CONNECT requests must not send :scheme or :path headers. + + + The request :scheme header '{requestScheme}' does not match the transport scheme '{transportScheme}'. + + + The Method '{method}' is invalid. + + + The request :path is invalid: '{path}' + A value greater than zero is required. diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 16a04e1cb7da..3a51915c656d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -32,7 +32,6 @@ internal class Http3Connection : IRequestProcessor, ITimeoutHandler private volatile bool _haveSentGoAway; private object _sync = new object(); private MultiplexedConnectionContext _multiplexedContext; - //private volatile bool _haveSentGoAway; private readonly Http3ConnectionContext _context; private readonly ISystemClock _systemClock; private readonly TimeoutControl _timeoutControl; @@ -222,7 +221,7 @@ public void OnTimeout(TimeoutReason reason) } } - private async Task InnerProcessRequestsAsync(IHttpApplication application) + internal async Task InnerProcessRequestsAsync(IHttpApplication application) { // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); @@ -247,6 +246,7 @@ private async Task InnerProcessRequestsAsync(IHttpApplication= 0) { - Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError); + Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3ErrorMethodInvalid(_methodText)), Http3ErrorCode.ProtocolError); return false; } } @@ -440,7 +441,7 @@ private bool TryValidatePath(ReadOnlySpan pathSegment) // Must start with a leading slash if (pathSegment.Length == 0 || pathSegment[0] != '/') { - Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError); + Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError); return false; } @@ -470,8 +471,7 @@ private bool TryValidatePath(ReadOnlySpan pathSegment) } catch (InvalidOperationException) { - // TODO change HTTP/2 specific messages to include HTTP/3 - Abort(new ConnectionAbortedException(CoreStrings.FormatHttp2StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError); + Abort(new ConnectionAbortedException(CoreStrings.FormatHttp3StreamErrorPathInvalid(RawTarget)), Http3ErrorCode.ProtocolError); return false; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs new file mode 100644 index 000000000000..de4a6d940615 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal class MultiplexedHeartbeatManager : IHeartbeatHandler, ISystemClock + { + private readonly MultiplexedConnectionManager _connectionManager; + private readonly Action _walkCallback; + private DateTimeOffset _now; + private long _nowTicks; + + public MultiplexedHeartbeatManager(MultiplexedConnectionManager connectionManager) + { + _connectionManager = connectionManager; + _walkCallback = WalkCallback; + } + + public DateTimeOffset UtcNow => new DateTimeOffset(UtcNowTicks, TimeSpan.Zero); + + public long UtcNowTicks => Volatile.Read(ref _nowTicks); + + public DateTimeOffset UtcNowUnsynchronized => _now; + + public void OnHeartbeat(DateTimeOffset now) + { + _now = now; + Volatile.Write(ref _nowTicks, now.Ticks); + + _connectionManager.Walk(_walkCallback); + } + + private void WalkCallback(MultiplexedKestrelConnection connection) + { + connection.TickHeartbeat(); + } + } +} diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index b1472f71182a..f01a3285bff9 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -93,9 +93,11 @@ private static ServiceContext CreateServiceContext(IOptions { CallBase = true }; + ConnectionAbortedException ex = null; + + mockConnectionContext.Setup(c => c.Abort(It.IsAny())) + .Callback(abortReason => ex = abortReason); + + mockConnectionContext.Object.Abort(); + + Assert.NotNull(ex); + Assert.Equal("The connection was aborted by the application via MultiplexedConnectionContext.Abort().", ex.Message); + } + } +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index 330f9a08b7e1..193503ab5756 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Text; using System.Threading.Tasks; using Microsoft.Net.Http.Headers; @@ -31,6 +32,22 @@ public async Task HelloWorldTest() Assert.Equal("Hello world", Encoding.ASCII.GetString(responseData.ToArray())); } + [Fact] + public async Task EmptyMethod_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, ""), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers); + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3ErrorMethodInvalid("")); + } + [Fact] public async Task RequestHeadersMaxRequestHeaderFieldSize_EndsStream() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index ae5c01f3a99b..540ef5ccaf2a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -19,9 +19,11 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; using Moq; using Xunit; using Xunit.Abstractions; +using static System.IO.Pipelines.DuplexPipe; using static Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2TestBase; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests @@ -38,6 +40,7 @@ public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable protected Task _connectionTask; protected readonly RequestDelegate _echoApplication; private TestMultiplexedConnectionContext _multiplexedContext; + private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); public Http3TestBase() { @@ -73,7 +76,8 @@ protected async Task InitializeConnectionAsync(RequestDelegate application) CreateConnection(); } - _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application)); + // Skip all heartbeat and lifetime notification feature registrations. + _connectionTask = _connection.InnerProcessRequestsAsync(new DummyApplication(application)); await Task.CompletedTask; } @@ -107,9 +111,8 @@ protected void CreateConnection() }; _connection = new Http3Connection(httpConnectionContext); - var httpConnection = new Http3Connection(httpConnectionContext); _mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny())) - .Callback(r => httpConnection.OnTimeout(r)); + .Callback(r => _connection.OnTimeout(r)); } private static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler writerScheduler) => new PipeOptions @@ -212,8 +215,7 @@ public Http3RequestStream(Http3TestBase testBase, Http3Connection connection) _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - StreamContext = new TestStreamContext(canRead: true, canWrite: true); - StreamContext.Transport = _pair.Transport; + StreamContext = new TestStreamContext(canRead: true, canWrite: true, _pair); } public async Task SendHeadersAsync(IEnumerable> headers) @@ -318,6 +320,12 @@ public void OnStaticIndexedHeader(int index, ReadOnlySpan value) { _decodedHeaders[((Span)H3StaticTable.Instance[index].Name).GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters(); } + + internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, string errorMessage) + { + var readResult = await _pair.Application.Input.ReadAsync(); + Assert.True(readResult.IsCompleted); + } } internal class Http3FrameWithPayload : Http3RawFrame @@ -347,12 +355,8 @@ public Http3ControlStream(Http3TestBase testBase) _testBase = testBase; var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); - _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - - StreamContext = new TestStreamContext(canRead: false, canWrite: true); - - StreamContext.Transport = _pair.Transport; + StreamContext = new TestStreamContext(canRead: false, canWrite: true, _pair); } public async Task WriteStreamIdAsync(int id) @@ -416,10 +420,12 @@ public override ValueTask ConnectAsync(IFeatureCollection feature private class TestStreamContext : StreamContext { - public TestStreamContext(bool canRead, bool canWrite) + private DuplexPipePair _pair; + public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair) { + _pair = pair; Features = new FeatureCollection(); - Features.Set(new TestStreamDirectionFeature(canRead, canWrite)); + Features.Set(new DefaultStreamDirectionFeature(canRead, canWrite)); } public override long StreamId { get; } @@ -430,19 +436,21 @@ public TestStreamContext(bool canRead, bool canWrite) public override IDictionary Items { get; set; } - public override IDuplexPipe Transport { get; set; } - - private class TestStreamDirectionFeature : IStreamDirectionFeature + public override IDuplexPipe Transport { - public TestStreamDirectionFeature(bool canRead, bool canWrite) + get { - CanRead = canRead; - CanWrite = canWrite; + return _pair.Transport; } + set + { + throw new NotImplementedException(); + } + } - public bool CanRead { get; } - - public bool CanWrite { get; } + public override void Abort(ConnectionAbortedException abortReason) + { + _pair.Application.Output.Complete(abortReason); } } } From 7455ae60fbae68bb6aa3f7ccf51e963259fdbe28 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 14 Feb 2020 17:05:14 -0800 Subject: [PATCH 11/27] more functionality working --- .../Internal/Http/HttpHeaders.Generated.cs | 2 +- .../Core/src/Internal/Http/HttpProtocol.cs | 2 +- .../Core/src/Internal/Http3/Http3Stream.cs | 14 ++-- src/Servers/Kestrel/Core/src/KestrelServer.cs | 65 ++++++++++++------- .../src/Internal/QuicConnectionListener.cs | 10 +++ .../Kestrel/samples/Http3SampleApp/Program.cs | 12 +++- 6 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs index c46e2183c5a8..ff6926ef336a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -12201,4 +12201,4 @@ public bool MoveNext() } } } -} \ No newline at end of file +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 47d6e3fa3275..35429cea0ffc 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -1196,7 +1196,7 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted) { foreach (var option in ServerOptions.ListenOptions) { - if (option.Protocols == HttpProtocols.Http3) + if ((option.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { responseHeaders.HeaderAltSvc = $"h3-25=\":{option.IPEndPoint.Port}\"; ma=84600"; break; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 8db63f350855..d77460fb5dfb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -175,10 +175,17 @@ public async Task ProcessRequestAsync(IHttpApplication appli var streamError = error as ConnectionAbortedException ?? new ConnectionAbortedException("The stream has completed.", error); - //await _appTask; + // Input has completed. + Input.Complete(); + _context.Transport.Input.CancelPendingRead(); + await RequestBodyPipe.Writer.CompleteAsync(); + + // Make sure application func is completed before completing writer. + await _appTask; + try { - //_frameWriter.Complete(); + _frameWriter.Complete(); } catch { @@ -187,9 +194,6 @@ public async Task ProcessRequestAsync(IHttpApplication appli } finally { - Input.Complete(); - _context.Transport.Input.CancelPendingRead(); - await RequestBodyPipe.Writer.CompleteAsync(); } } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index f01a3285bff9..c9466aa233f6 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -148,9 +149,32 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions options) { + // INVESTIGATE: For some reason, Quic needs to bind to + if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) + { + if (_multiplexedTransportFactories == null) + { + throw new InvalidOperationException("Cannot start HTTP/3 server if no MultiplexedTransportFactories are registered."); + } + + options.UseHttp3Server(ServiceContext, application, options.Protocols); + var multiplxedConnectionDelegate = options.BuildMultiplexed(); + + var multiplexedConnectionDispatcher = new MultiplexedConnectionDispatcher(ServiceContext, multiplxedConnectionDelegate); + var multiplexedFactory = _multiplexedTransportFactories.Last(); + var multiplexedTransport = await multiplexedFactory.BindAsync(options.EndPoint).ConfigureAwait(false); + + var acceptLoopTask = multiplexedConnectionDispatcher.StartAcceptingConnections(multiplexedTransport); + _multiplexedTransports.Add((multiplexedTransport, acceptLoopTask)); + + options.EndPoint = multiplexedTransport.EndPoint; + } + // Add the HTTP middleware as the terminal connection middleware if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1 - || (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2) + || (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2 + || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place + // when there is no HttpProtocols in KestrelServer, can we remove/change the test? { options.UseHttpServer(ServiceContext, application, options.Protocols); var connectionDelegate = options.Build(); @@ -170,26 +194,6 @@ async Task OnBind(ListenOptions options) _transports.Add((transport, acceptLoopTask)); options.EndPoint = transport.EndPoint; } - - if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) - { - if (_multiplexedTransportFactories == null) - { - throw new InvalidOperationException("Cannot start HTTP/3 server if no MultiplexedTransportFactories are registered."); - } - - options.UseHttp3Server(ServiceContext, application, options.Protocols); - var multiplxedConnectionDelegate = options.BuildMultiplexed(); - - var multiplexedConnectionDispatcher = new MultiplexedConnectionDispatcher(ServiceContext, multiplxedConnectionDelegate); - var multiplexedFactory = _multiplexedTransportFactories.Last(); - var multiplexedTransport = await multiplexedFactory.BindAsync(options.EndPoint).ConfigureAwait(false); - - var acceptLoopTask = multiplexedConnectionDispatcher.StartAcceptingConnections(multiplexedTransport); - _multiplexedTransports.Add((multiplexedTransport, acceptLoopTask)); - - options.EndPoint = multiplexedTransport.EndPoint; - } } await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false); @@ -213,13 +217,22 @@ public async Task StopAsync(CancellationToken cancellationToken) try { - var tasks = new Task[_transports.Count]; - for (int i = 0; i < _transports.Count; i++) + var transportCount = _transports.Count; + var totalTransportCount = _transports.Count + _multiplexedTransports.Count; + var tasks = new Task[totalTransportCount]; + + for (int i = 0; i < transportCount; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); } + for (int i = transportCount; i < totalTransportCount; i++) + { + (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - transportCount]; + tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); + } + await Task.WhenAll(tasks).ConfigureAwait(false); if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) @@ -238,6 +251,12 @@ public async Task StopAsync(CancellationToken cancellationToken) tasks[i] = listener.DisposeAsync().AsTask(); } + for (int i = _transports.Count; i < totalTransportCount; i++) + { + (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - transportCount]; + tasks[i] = listener.DisposeAsync().AsTask(); + } + await Task.WhenAll(tasks).ConfigureAwait(false); ServiceContext.Heartbeat?.Dispose(); diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs index 5a0df2aa2e83..71866dc2959e 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -39,6 +39,15 @@ public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndP public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); + try + { + _ = quicConnection.LocalEndPoint; + } + catch (Exception) + { + return null; + } + return new QuicConnectionContext(quicConnection, _context); } @@ -56,6 +65,7 @@ public ValueTask DisposeAsync() _disposed = true; + _listener.Close(); _listener.Dispose(); return new ValueTask(); diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index 3847204a06c1..f3229a0da787 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -35,15 +35,25 @@ public static void Main(string[] args) { var basePort = 5557; options.EnableAltSvc = true; + options.Listen(IPAddress.Any, basePort, listenOptions => { listenOptions.UseHttps(httpsOptions => { httpsOptions.ServerCertificate = cert; }); - //listenOptions.Protocols = HttpProtocols.Http3; listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); + //options.Listen(IPAddress.Any, basePort, listenOptions => + //{ + // listenOptions.UseHttps(httpsOptions => + // { + // httpsOptions.ServerCertificate = cert; + // }); + // listenOptions.Protocols = HttpProtocols.Http3; + // //listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + //}); + }) .UseStartup(); }); From 45b915350d2ef593ce778405d85d789d363af194 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Sat, 15 Feb 2020 14:07:39 -0800 Subject: [PATCH 12/27] Add way to pass protocol error code to streams --- .../src/Features/IProtocolErrorCodeFeature.cs | 10 ++++ .../Core/src/Internal/Http3/Http3Stream.cs | 9 +++- src/Servers/Kestrel/Core/src/KestrelServer.cs | 3 +- .../Transport.Quic/src/Internal/IQuicTrace.cs | 1 + .../src/Internal/QuicStreamContext.cs | 10 ++-- .../Transport.Quic/src/Internal/QuicTrace.cs | 9 +++- .../src/QuicTransportOptions.cs | 5 -- .../Http3/Http3StreamTests.cs | 41 ++++++++++++++- .../Http3/Http3TestBase.cs | 52 ++++++++++++++----- 9 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 src/Servers/Connections.Abstractions/src/Features/IProtocolErrorCodeFeature.cs diff --git a/src/Servers/Connections.Abstractions/src/Features/IProtocolErrorCodeFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IProtocolErrorCodeFeature.cs new file mode 100644 index 000000000000..c47a2485b86d --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/Features/IProtocolErrorCodeFeature.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Connections.Features +{ + public interface IProtocolErrorCodeFeature + { + long Error { get; set; } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index d77460fb5dfb..07454d275d80 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -26,6 +27,7 @@ internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThread private int _isClosed; private int _gracefulCloseInitiator; private readonly Http3StreamContext _context; + private readonly IProtocolErrorCodeFeature _errorCodeFeature; private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); private readonly Http3Connection _http3Connection; @@ -43,6 +45,8 @@ public Http3Stream(Http3Connection http3Connection, Http3StreamContext context) _http3Connection = http3Connection; _context = context; + _errorCodeFeature = _context.ConnectionFeatures.Get(); + _frameWriter = new Http3FrameWriter( context.Transport.Output, context.StreamContext, @@ -79,6 +83,7 @@ public void Abort(ConnectionAbortedException ex) public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode) { + _errorCodeFeature.Error = (long)errorCode; _frameWriter.Abort(ex); // TODO figure out how to get error code in the right spot. } @@ -115,7 +120,7 @@ public void HandleRequestHeadersTimeout() public void OnInputOrOutputCompleted() { TryClose(); - _frameWriter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient)); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), Http3ErrorCode.NoError); } private bool TryClose() @@ -189,7 +194,7 @@ public async Task ProcessRequestAsync(IHttpApplication appli } catch { - _frameWriter.Abort(streamError); + Abort(streamError, Http3ErrorCode.ProtocolError); throw; } finally diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index c9466aa233f6..78e085ea8ad0 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -149,7 +149,8 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions options) { - // INVESTIGATE: For some reason, Quic needs to bind to + // INVESTIGATE: For some reason, MsQuic needs to bind before + // sockets for it to successfully listen. It also seems racy. if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { if (_multiplexedTransportFactories == null) diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs index c70d461430be..4bd88a52d5b0 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs @@ -15,5 +15,6 @@ internal interface IQuicTrace : ILogger void StreamPause(string streamId); void StreamResume(string streamId); void StreamShutdownWrite(string streamId, Exception ex); + void StreamAbort(string streamId, Exception ex); } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs index 1c6e36f0f8c0..1aafa00941e3 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal class QuicStreamContext : TransportStream, IStreamDirectionFeature + internal class QuicStreamContext : TransportStream, IStreamDirectionFeature, IProtocolErrorCodeFeature { private readonly Task _processingTask; private readonly QuicStream _stream; @@ -47,6 +47,7 @@ public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, Qu var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); Features.Set(this); + Features.Set(this); // TODO populate the ITlsConnectionFeature (requires client certs). Features.Set(new FakeTlsConnectionFeature()); @@ -90,6 +91,7 @@ public override string ConnectionId } } + public long Error { get; set; } private async Task StartAsync() { @@ -282,10 +284,12 @@ private async Task ProcessSends() public override void Abort(ConnectionAbortedException abortReason) { // Don't call _stream.Shutdown and _stream.Abort at the same time. + _log.StreamAbort(ConnectionId, abortReason); + lock (_shutdownLock) { - _stream.AbortRead(_context.Options.AbortErrorCode); - _stream.AbortWrite(_context.Options.AbortErrorCode); + _stream.AbortRead(Error); + _stream.AbortWrite(Error); } // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs index 18e5d8dc6d3b..6adf0667c02d 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs @@ -21,7 +21,9 @@ internal class QuicTrace : IQuicTrace private static readonly Action _streamResume = LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamResume)), @"Stream id ""{ConnectionId}"" resumed."); private static readonly Action _streamShutdownWrite = - LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Connection id ""{ConnectionId}"" shutting down writes, exception: ""{Reason}""."); + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Stream id ""{ConnectionId}"" shutting down writes, exception: ""{Reason}""."); + private static readonly Action _streamAborted = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Stream id ""{ConnectionId}"" aborted by application, exception: ""{Reason}""."); private ILogger _logger; @@ -70,5 +72,10 @@ public void StreamShutdownWrite(string streamId, Exception ex) { _streamShutdownWrite(_logger, streamId, ex.Message, ex); } + + public void StreamAbort(string streamId, Exception ex) + { + _streamAborted(_logger, streamId, ex.Message, ex); + } } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs index 758005d7f256..985222d3d1b4 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs @@ -44,11 +44,6 @@ public class QuicTransportOptions /// public long? MaxWriteBufferSize { get; set; } = 64 * 1024; - /// - /// The error code to abort with - /// - public long AbortErrorCode { get; set; } = 0; - internal Func> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create; } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index 193503ab5756..7b45c21031a8 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -25,7 +25,7 @@ public async Task HelloWorldTest() var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication); var doneWithHeaders = await requestStream.SendHeadersAsync(headers); - await requestStream.SendDataAsync(Encoding.ASCII.GetBytes("Hello world")); + await requestStream.SendDataAsync(Encoding.ASCII.GetBytes("Hello world"), endStream: true); var responseHeaders = await requestStream.ExpectHeadersAsync(); var responseData = await requestStream.ExpectDataAsync(); @@ -48,6 +48,45 @@ public async Task EmptyMethod_Reset() await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3ErrorMethodInvalid("")); } + [Fact] + public async Task InvalidCustomMethod_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Hello,World"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers); + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3ErrorMethodInvalid("Hello,World")); + } + + [Fact] + public async Task CustomMethod_Accepted() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "Custom"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoMethod); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("Custom", responseHeaders["Method"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + [Fact] public async Task RequestHeadersMaxRequestHeaderFieldSize_EndsStream() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index 540ef5ccaf2a..5e3f9b8c2a23 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -16,10 +16,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Logging; using Moq; using Xunit; using Xunit.Abstractions; @@ -38,15 +36,21 @@ public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable internal readonly Mock _mockTimeoutControl; internal readonly MemoryPool _memoryPool = SlabMemoryPoolFactory.Create(); protected Task _connectionTask; - protected readonly RequestDelegate _echoApplication; private TestMultiplexedConnectionContext _multiplexedContext; private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); + protected readonly RequestDelegate _noopApplication; + protected readonly RequestDelegate _echoApplication; + protected readonly RequestDelegate _echoMethod; + public Http3TestBase() { _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object); _mockTimeoutControl = new Mock(_timeoutControl) { CallBase = true }; _timeoutControl.Debugger = Mock.Of(); + + _noopApplication = context => Task.CompletedTask; + _echoApplication = async context => { var buffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize]; @@ -57,6 +61,13 @@ public Http3TestBase() await context.Response.Body.WriteAsync(buffer, 0, received); } }; + + _echoMethod = context => + { + context.Response.Headers["Method"] = context.Request.Method; + + return Task.CompletedTask; + }; } public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) @@ -191,7 +202,7 @@ protected static async Task FlushAsync(PipeWriter writableBuffer) } } - internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler + internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler, IProtocolErrorCodeFeature { internal StreamContext StreamContext { get; } @@ -200,6 +211,8 @@ internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler public long StreamId => 0; + public long Error { get; set; } + private readonly byte[] _headerEncodingBuffer = new byte[Http3PeerSettings.MinAllowedMaxFrameSize]; private QPackEncoder _qpackEncoder = new QPackEncoder(); private QPackDecoder _qpackDecoder = new QPackDecoder(8192); @@ -214,11 +227,11 @@ public Http3RequestStream(Http3TestBase testBase, Http3Connection connection) var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - - StreamContext = new TestStreamContext(canRead: true, canWrite: true, _pair); + + StreamContext = new TestStreamContext(canRead: true, canWrite: true, _pair, this); } - public async Task SendHeadersAsync(IEnumerable> headers) + public async Task SendHeadersAsync(IEnumerable> headers, bool endStream = false) { var outputWriter = _pair.Application.Output; var frame = new Http3RawFrame(); @@ -229,10 +242,16 @@ public async Task SendHeadersAsync(IEnumerable data) + internal async Task SendDataAsync(Memory data, bool endStream = false) { var outputWriter = _pair.Application.Output; var frame = new Http3RawFrame(); @@ -240,9 +259,14 @@ internal async Task SendDataAsync(Memory data) frame.Length = data.Length; Http3FrameWriter.WriteHeader(frame, outputWriter); await SendAsync(data.Span); + + if (endStream) + { + await _pair.Application.Output.CompleteAsync(); + } } - internal async Task>> ExpectHeadersAsync() + internal async Task> ExpectHeadersAsync() { var http3WithPayload = await ReceiveFrameAsync(); _qpackDecoder.Decode(http3WithPayload.PayloadSequence, this); @@ -325,6 +349,7 @@ internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, string { var readResult = await _pair.Application.Input.ReadAsync(); Assert.True(readResult.IsCompleted); + Assert.Equal((long)protocolError, Error); } } @@ -341,7 +366,7 @@ public Http3FrameWithPayload() : base() } - internal class Http3ControlStream : Http3StreamBase + internal class Http3ControlStream : Http3StreamBase, IProtocolErrorCodeFeature { internal StreamContext StreamContext { get; } @@ -350,13 +375,15 @@ internal class Http3ControlStream : Http3StreamBase public long StreamId => 0; + public long Error { get; set; } + public Http3ControlStream(Http3TestBase testBase) { _testBase = testBase; var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - StreamContext = new TestStreamContext(canRead: false, canWrite: true, _pair); + StreamContext = new TestStreamContext(canRead: false, canWrite: true, _pair, this); } public async Task WriteStreamIdAsync(int id) @@ -421,11 +448,12 @@ public override ValueTask ConnectAsync(IFeatureCollection feature private class TestStreamContext : StreamContext { private DuplexPipePair _pair; - public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair) + public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair, IProtocolErrorCodeFeature feature) { _pair = pair; Features = new FeatureCollection(); Features.Set(new DefaultStreamDirectionFeature(canRead, canWrite)); + Features.Set(feature); } public override long StreamId { get; } From bf0646333dabd50671555a0593695bd4496f52aa Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 18 Feb 2020 10:54:22 -0800 Subject: [PATCH 13/27] Many tests --- src/Servers/Kestrel/Core/src/CoreStrings.resx | 6 + .../Core/src/Internal/Http/HttpProtocol.cs | 2 +- .../src/Internal/Http3/Http3FrameWriter.cs | 2 + .../Core/src/Internal/Http3/Http3Stream.cs | 244 ++++++++- .../Http3/Http3StreamTests.cs | 495 ++++++++++++++++++ .../Http3/Http3TestBase.cs | 28 +- .../runtime/Http3/QPack/QPackEncoder.cs | 7 +- 7 files changed, 777 insertions(+), 7 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 4a5d12f26468..f84ed1d2cef1 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -539,6 +539,12 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l The request :path is invalid: '{path}' + + Less data received than specified in the Content-Length header. + + + More data received than specified in the Content-Length header. + A value greater than zero is required. diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 35429cea0ffc..8ca14493dc69 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -511,7 +511,7 @@ private void PreventRequestAbortedCancellation() } } - public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) + public virtual void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { _requestHeadersParsed++; if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs index bb2e33a4cbbb..34ab8c899486 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs @@ -308,6 +308,8 @@ public void Complete() return; } + _log.ConnectionStop("TEST123 " + Environment.StackTrace); + _completed = true; _outputWriter.Complete(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 07454d275d80..7a8836eb4eb6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -22,6 +22,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 { internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThreadPoolWorkItem { + private static ReadOnlySpan AuthorityBytes => new byte[10] { (byte)':', (byte)'a', (byte)'u', (byte)'t', (byte)'h', (byte)'o', (byte)'r', (byte)'i', (byte)'t', (byte)'y' }; + private static ReadOnlySpan MethodBytes => new byte[7] { (byte)':', (byte)'m', (byte)'e', (byte)'t', (byte)'h', (byte)'o', (byte)'d' }; + private static ReadOnlySpan PathBytes => new byte[5] { (byte)':', (byte)'p', (byte)'a', (byte)'t', (byte)'h' }; + private static ReadOnlySpan SchemeBytes => new byte[7] { (byte)':', (byte)'s', (byte)'c', (byte)'h', (byte)'e', (byte)'m', (byte)'e' }; + private static ReadOnlySpan StatusBytes => new byte[7] { (byte)':', (byte)'s', (byte)'t', (byte)'a', (byte)'t', (byte)'u', (byte)'s' }; + private static ReadOnlySpan ConnectionBytes => new byte[10] { (byte)'c', (byte)'o', (byte)'n', (byte)'n', (byte)'e', (byte)'c', (byte)'t', (byte)'i', (byte)'o', (byte)'n' }; + private static ReadOnlySpan TeBytes => new byte[2] { (byte)'t', (byte)'e' }; + private static ReadOnlySpan TrailersBytes => new byte[8] { (byte)'t', (byte)'r', (byte)'a', (byte)'i', (byte)'l', (byte)'e', (byte)'r', (byte)'s' }; + private static ReadOnlySpan ConnectBytes => new byte[7] { (byte)'C', (byte)'O', (byte)'N', (byte)'N', (byte)'E', (byte)'C', (byte)'T' }; + private Http3FrameWriter _frameWriter; private Http3OutputProducer _http3Output; private int _isClosed; @@ -29,6 +39,9 @@ internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThread private readonly Http3StreamContext _context; private readonly IProtocolErrorCodeFeature _errorCodeFeature; private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); + private RequestHeaderParsingState _requestHeaderParsingState; + private PseudoHeaderFields _parsedPseudoHeaderFields; + private bool _isMethodConnect; private readonly Http3Connection _http3Connection; private bool _receivedHeaders; @@ -39,6 +52,9 @@ internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThread public Http3Stream(Http3Connection http3Connection, Http3StreamContext context) { Initialize(context); + + InputRemaining = null; + // First, determine how we know if an Http3stream is unidirectional or bidirectional var httpLimits = context.ServiceContext.ServerOptions.Limits; var http3Limits = httpLimits.Http3; @@ -69,6 +85,8 @@ public Http3Stream(Http3Connection http3Connection, Http3StreamContext context) QPackDecoder = new QPackDecoder(_context.ServiceContext.ServerOptions.Limits.Http3.MaxRequestHeaderFieldSize); } + public long? InputRemaining { get; internal set; } + public QPackDecoder QPackDecoder { get; } public PipeReader Input => _context.Transport.Input; @@ -84,8 +102,9 @@ public void Abort(ConnectionAbortedException ex) public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode) { _errorCodeFeature.Error = (long)errorCode; + // TODO replace with IKestrelTrace log. + Log.LogWarning(ex, ex.Message); _frameWriter.Abort(ex); - // TODO figure out how to get error code in the right spot. } public void OnHeadersComplete(bool endStream) @@ -105,6 +124,165 @@ public void OnStaticIndexedHeader(int index, ReadOnlySpan value) OnHeader(knownHeader.Name, value); } + public override void OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + // TODO MaxRequestHeadersTotalSize? + ValidateHeader(name, value); + try + { + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + { + OnTrailer(name, value); + } + else + { + // Throws BadRequest for header count limit breaches. + // Throws InvalidOperation for bad encoding. + base.OnHeader(name, value); + } + } + catch (BadHttpRequestException bre) + { + throw new Http3StreamErrorException(bre.Message, Http3ErrorCode.ProtocolError); + } + catch (InvalidOperationException) + { + throw new Http3StreamErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http3ErrorCode.ProtocolError); + } + } + + private void ValidateHeader(ReadOnlySpan name, ReadOnlySpan value) + { + // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1 + /* + Intermediaries that process HTTP requests or responses (i.e., any + intermediary not acting as a tunnel) MUST NOT forward a malformed + request or response. Malformed requests or responses that are + detected MUST be treated as a stream error (Section 5.4.2) of type + PROTOCOL_ERROR. + + For malformed requests, a server MAY send an HTTP response prior to + closing or resetting the stream. Clients MUST NOT accept a malformed + response. Note that these requirements are intended to protect + against several types of common attacks against HTTP; they are + deliberately strict because being permissive can expose + implementations to these vulnerabilities.*/ + if (IsPseudoHeaderField(name, out var headerField)) + { + if (_requestHeaderParsingState == RequestHeaderParsingState.Headers) + { + // All pseudo-header fields MUST appear in the header block before regular header fields. + // Any request or response that contains a pseudo-header field that appears in a header + // block after a regular header field MUST be treated as malformed (Section 8.1.2.6). + throw new Http3StreamErrorException(CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http3ErrorCode.ProtocolError); + } + + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + { + // Pseudo-header fields MUST NOT appear in trailers. + throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailersContainPseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + _requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields; + + if (headerField == PseudoHeaderFields.Unknown) + { + // Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header + // fields as malformed (Section 8.1.2.6). + throw new Http3StreamErrorException(CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + if (headerField == PseudoHeaderFields.Status) + { + // Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields + // defined for responses MUST NOT appear in requests. + throw new Http3StreamErrorException(CoreStrings.Http2ErrorResponsePseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + if ((_parsedPseudoHeaderFields & headerField) == headerField) + { + // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3 + // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields + throw new Http3StreamErrorException(CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http3ErrorCode.ProtocolError); + } + + if (headerField == PseudoHeaderFields.Method) + { + _isMethodConnect = value.SequenceEqual(ConnectBytes); + } + + _parsedPseudoHeaderFields |= headerField; + } + else if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers) + { + _requestHeaderParsingState = RequestHeaderParsingState.Headers; + } + + if (IsConnectionSpecificHeaderField(name, value)) + { + throw new Http3StreamErrorException(CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http3ErrorCode.ProtocolError); + } + + // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2 + // A request or response containing uppercase header field names MUST be treated as malformed (Section 8.1.2.6). + for (var i = 0; i < name.Length; i++) + { + if (name[i] >= 65 && name[i] <= 90) + { + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + { + throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailerNameUppercase, Http3ErrorCode.ProtocolError); + } + else + { + throw new Http3StreamErrorException(CoreStrings.Http2ErrorHeaderNameUppercase, Http3ErrorCode.ProtocolError); + } + } + } + } + + private bool IsPseudoHeaderField(ReadOnlySpan name, out PseudoHeaderFields headerField) + { + headerField = PseudoHeaderFields.None; + + if (name.IsEmpty || name[0] != (byte)':') + { + return false; + } + + if (name.SequenceEqual(PathBytes)) + { + headerField = PseudoHeaderFields.Path; + } + else if (name.SequenceEqual(MethodBytes)) + { + headerField = PseudoHeaderFields.Method; + } + else if (name.SequenceEqual(SchemeBytes)) + { + headerField = PseudoHeaderFields.Scheme; + } + else if (name.SequenceEqual(StatusBytes)) + { + headerField = PseudoHeaderFields.Status; + } + else if (name.SequenceEqual(AuthorityBytes)) + { + headerField = PseudoHeaderFields.Authority; + } + else + { + headerField = PseudoHeaderFields.Unknown; + } + + return true; + } + + private static bool IsConnectionSpecificHeaderField(ReadOnlySpan name, ReadOnlySpan value) + { + return name.SequenceEqual(ConnectionBytes) || (name.SequenceEqual(TeBytes) && !value.SequenceEqual(TrailersBytes)); + } + public void HandleReadDataRateTimeout() { Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond); @@ -119,6 +297,7 @@ public void HandleRequestHeadersTimeout() public void OnInputOrOutputCompleted() { + Log.LogTrace("On input or output completed"); TryClose(); Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), Http3ErrorCode.NoError); } @@ -160,6 +339,7 @@ public async Task ProcessRequestAsync(IHttpApplication appli if (result.IsCompleted) { + OnEndStreamReceived(); return; } } @@ -170,6 +350,13 @@ public async Task ProcessRequestAsync(IHttpApplication appli } } } + catch (Http3StreamErrorException ex) + { + error = ex; + //errorCode = ex.ErrorCode; + Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode); + Log.LogWarning(0, ex, "Stream threw an exception."); + } catch (Exception ex) { error = ex; @@ -181,6 +368,7 @@ public async Task ProcessRequestAsync(IHttpApplication appli ?? new ConnectionAbortedException("The stream has completed.", error); // Input has completed. + Input.Complete(); _context.Transport.Input.CancelPendingRead(); await RequestBodyPipe.Writer.CompleteAsync(); @@ -203,6 +391,21 @@ public async Task ProcessRequestAsync(IHttpApplication appli } } + private void OnEndStreamReceived() + { + Log.LogTrace("OnEndStreamReceived"); + if (InputRemaining.HasValue) + { + // https://tools.ietf.org/html/rfc7540#section-8.1.2.6 + if (InputRemaining.Value != 0) + { + throw new Http3StreamErrorException(CoreStrings.Http3StreamErrorLessDataThanLength, Http3ErrorCode.ProtocolError); + } + } + + OnTrailersComplete(); + RequestBodyPipe.Writer.Complete(); + } private Task ProcessHttp3Stream(IHttpApplication application, in ReadOnlySequence payload) { @@ -245,6 +448,7 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli } _receivedHeaders = true; + InputRemaining = HttpRequestHeaders.ContentLength; _appTask = Task.Run(() => base.ProcessRequestsAsync(application)); return Task.CompletedTask; @@ -252,6 +456,17 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli private Task ProcessDataFrameAsync(in ReadOnlySequence payload) { + if (InputRemaining.HasValue) + { + // https://tools.ietf.org/html/rfc7540#section-8.1.2.6 + if (payload.Length > InputRemaining.Value) + { + throw new Http3StreamErrorException(CoreStrings.Http3StreamErrorMoreDataThanLength, Http3ErrorCode.ProtocolError); + } + + InputRemaining -= payload.Length; + } + foreach (var segment in payload) { RequestBodyPipe.Writer.Write(segment.Span); @@ -284,6 +499,8 @@ protected override void OnReset() protected override void ApplicationAbort() { + var abortReason = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication); + Abort(abortReason, Http3ErrorCode.InternalError); } protected override string CreateRequestId() @@ -340,8 +557,8 @@ private bool TryValidatePseudoHeaders() // - We'll need to find some concrete scenarios to warrant unblocking this. if (!string.Equals(RequestHeaders[HeaderNames.Scheme], Scheme, StringComparison.OrdinalIgnoreCase)) { - Abort(new ConnectionAbortedException( - CoreStrings.FormatHttp3StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme)), Http3ErrorCode.ProtocolError); + var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme); + Abort(new ConnectionAbortedException(str), Http3ErrorCode.ProtocolError); return false; } @@ -504,6 +721,27 @@ private Pipe CreateRequestBodyPipe(uint windowSize) /// public abstract void Execute(); + private enum RequestHeaderParsingState + { + Ready, + PseudoHeaderFields, + Headers, + Trailers + } + + + [Flags] + private enum PseudoHeaderFields + { + None = 0x0, + Authority = 0x1, + Method = 0x2, + Path = 0x4, + Scheme = 0x8, + Status = 0x10, + Unknown = 0x40000000 + } + private static class GracefulCloseInitiator { public const int None = 0; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index 7b45c21031a8..b255842a3af7 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Net.Http.Headers; using Xunit; @@ -107,5 +108,499 @@ public async Task RequestHeadersMaxRequestHeaderFieldSize_EndsStream() // TODO figure out how to test errors for request streams that would be set on the Quic Stream. await requestStream.ExpectReceiveEndOfStream(); } + + [Fact] + public async Task ConnectMethod_Accepted() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_echoMethod); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "CONNECT") }; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("CONNECT", responseHeaders["Method"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task OptionsStar_LeftOutOfPath() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_echoPath); + var headers = new[] { new KeyValuePair(HeaderNames.Method, "OPTIONS"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, "*")}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(5, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("", responseHeaders["path"]); + Assert.Equal("*", responseHeaders["rawtarget"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task OptionsSlash_Accepted() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_echoPath); + + var headers = new[] { new KeyValuePair(HeaderNames.Method, "OPTIONS"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, "/")}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(5, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("/", responseHeaders["path"]); + Assert.Equal("/", responseHeaders["rawtarget"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task PathAndQuery_Separated() + { + var requestStream = await InitializeConnectionAndStreamsAsync(context => + { + context.Response.Headers["path"] = context.Request.Path.Value; + context.Response.Headers["query"] = context.Request.QueryString.Value; + context.Response.Headers["rawtarget"] = context.Features.Get().RawTarget; + return Task.CompletedTask; + }); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, "/a/path?a&que%35ry")}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(6, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("/a/path", responseHeaders["path"]); + Assert.Equal("?a&que%35ry", responseHeaders["query"]); + Assert.Equal("/a/path?a&que%35ry", responseHeaders["rawtarget"]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Theory] + [InlineData("/", "/")] + [InlineData("/a%5E", "/a^")] + [InlineData("/a%E2%82%AC", "/a€")] + [InlineData("/a%2Fb", "/a%2Fb")] // Forward slash, not decoded + [InlineData("/a%b", "/a%b")] // Incomplete encoding, not decoded + [InlineData("/a/b/c/../d", "/a/b/d")] // Navigation processed + [InlineData("/a/b/c/../../../../d", "/d")] // Navigation escape prevented + [InlineData("/a/b/c/.%2E/d", "/a/b/d")] // Decode before navigation processing + public async Task Path_DecodedAndNormalized(string input, string expected) + { + var requestStream = await InitializeConnectionAndStreamsAsync(context => + { + Assert.Equal(expected, context.Request.Path.Value); + Assert.Equal(input, context.Features.Get().RawTarget); + return Task.CompletedTask; + }); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Path, input)}; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Theory] + [InlineData(":path", "/")] + [InlineData(":scheme", "http")] + public async Task ConnectMethod_WithSchemeOrPath_Reset(string headerName, string value) + { + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "CONNECT"), + new KeyValuePair(headerName, value) }; + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.Http3ErrorConnectMustNotSendSchemeOrPath); + } + + [Fact] + public async Task SchemeMismatch_Reset() + { + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + // :path and :scheme are not allowed, :authority is optional + var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "https") }; // Not the expected "http" + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.FormatHttp3StreamErrorSchemeMismatch("https", "http")); + } + + [Fact] + public async Task MissingAuthority_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + await InitializeConnectionAsync(_noopApplication); + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task EmptyAuthority_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, ""), + }; + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders["content-length"]); + } + + [Fact] + public async Task MissingAuthorityFallsBackToHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("abc", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task EmptyAuthorityIgnoredOverHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, ""), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("abc", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task AuthorityOverridesHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "def"), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("def", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task AuthorityOverridesInvalidHost_200Status() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "def"), + new KeyValuePair("Host", "a=bc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_echoHost); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(4, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + Assert.Equal("def", responseHeaders[HeaderNames.Host]); + } + + [Fact] + public async Task InvalidAuthority_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "local=host:80"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("local=host:80")); + } + + [Fact] + public async Task InvalidAuthorityWithValidHost_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "d=ef"), + new KeyValuePair("Host", "abc"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("d=ef")); + } + + [Fact] + public async Task TwoHosts_StreamReset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair("Host", "host1"), + new KeyValuePair("Host", "host2"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("host1,host2")); + } + + [Fact] + public async Task MaxRequestLineSize_Reset() + { + // Default 8kb limit + // This test has to work around the HPack parser limit for incoming field sizes over 4kb. That's going to be a problem for people with long urls. + // https://github.com/aspnet/KestrelHttpServer/issues/2872 + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET" + new string('a', 1024 * 3)), + new KeyValuePair(HeaderNames.Path, "/Hello/How/Are/You/" + new string('a', 1024 * 3)), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost" + new string('a', 1024 * 3) + ":80"), + }; + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, + CoreStrings.BadRequest_RequestLineTooLong); + } + + [Fact] + public async Task ContentLength_Received_SingleDataFrame_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + Assert.Equal(12, read); + read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + Assert.Equal(0, read); + }); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false); + await requestStream.SendDataAsync(new byte[12], endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task ContentLength_Received_MultipleDataFrame_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + var total = read; + while (read > 0) + { + read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total); + total += read; + } + Assert.Equal(12, total); + }); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false); + + await requestStream.SendDataAsync(new byte[1], endStream: false); + await requestStream.SendDataAsync(new byte[3], endStream: false); + await requestStream.SendDataAsync(new byte[8], endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + } + + [Fact] + public async Task ContentLength_Received_MultipleDataFrame_ReadViaPipe_Verified() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + var requestStream = await InitializeConnectionAndStreamsAsync(async context => + { + var readResult = await context.Request.BodyReader.ReadAsync(); + while (!readResult.IsCompleted) + { + context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); + readResult = await context.Request.BodyReader.ReadAsync(); + } + + Assert.Equal(12, readResult.Buffer.Length); + context.Request.BodyReader.AdvanceTo(readResult.Buffer.End); + }); + + var doneWithHeaders = await requestStream.SendHeadersAsync(headers, endStream: false); + + await requestStream.SendDataAsync(new byte[1], endStream: false); + await requestStream.SendDataAsync(new byte[3], endStream: false); + await requestStream.SendDataAsync(new byte[8], endStream: true); + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[HeaderNames.Status]); + Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); + } + + + [Fact(Skip = "Http3OutputProducer.Complete is called before input recognizes there is an error. Why is this different than HTTP/2?")] + public async Task ContentLength_Received_NoDataFrames_Reset() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.ContentLength, "12"), + }; + + var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication); + + await requestStream.SendHeadersAsync(headers, endStream: true); + + await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.Http3StreamErrorLessDataThanLength); + } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index 5e3f9b8c2a23..fb52bc77f9db 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -18,6 +18,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; using Moq; using Xunit; using Xunit.Abstractions; @@ -42,6 +44,8 @@ public class Http3TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable protected readonly RequestDelegate _noopApplication; protected readonly RequestDelegate _echoApplication; protected readonly RequestDelegate _echoMethod; + protected readonly RequestDelegate _echoPath; + protected readonly RequestDelegate _echoHost; public Http3TestBase() { @@ -68,6 +72,21 @@ public Http3TestBase() return Task.CompletedTask; }; + + _echoPath = context => + { + context.Response.Headers["path"] = context.Request.Path.ToString(); + context.Response.Headers["rawtarget"] = context.Features.Get().RawTarget; + + return Task.CompletedTask; + }; + + _echoHost = context => + { + context.Response.Headers[HeaderNames.Host] = context.Request.Headers[HeaderNames.Host]; + + return Task.CompletedTask; + }; } public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) @@ -345,11 +364,18 @@ public void OnStaticIndexedHeader(int index, ReadOnlySpan value) _decodedHeaders[((Span)H3StaticTable.Instance[index].Name).GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters(); } - internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, string errorMessage) + internal async Task WaitForStreamErrorAsync(Http3ErrorCode protocolError, string expectedErrorMessage) { var readResult = await _pair.Application.Input.ReadAsync(); + _testBase.Logger.LogTrace("Input is completed"); + Assert.True(readResult.IsCompleted); Assert.Equal((long)protocolError, Error); + + if (expectedErrorMessage != null) + { + Assert.Contains(_testBase.TestApplicationErrorLogger.Messages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false); + } } } diff --git a/src/Shared/runtime/Http3/QPack/QPackEncoder.cs b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs index 348106aa15b2..ecf0f1e183ab 100644 --- a/src/Shared/runtime/Http3/QPack/QPackEncoder.cs +++ b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs @@ -340,7 +340,6 @@ private static bool EncodeHeaderBlockPrefix(Span destination, out int byte return true; } - // TODO these are fairly hard coded for the first two bytes to be zero. public bool BeginEncode(IEnumerable> headers, Span buffer, out int length) { _enumerator = headers.GetEnumerator(); @@ -351,7 +350,11 @@ public bool BeginEncode(IEnumerable> headers, Span< buffer[0] = 0; buffer[1] = 0; - return Encode(buffer.Slice(2), out length); + bool doneEncode = Encode(buffer.Slice(2), out length); + + // Add two for the first two bytes. + length += 2; + return doneEncode; } public bool BeginEncode(int statusCode, IEnumerable> headers, Span buffer, out int length) From 1d93bba53f51c062c2325953cbe85a0cfcf5495e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 18 Feb 2020 10:56:09 -0800 Subject: [PATCH 14/27] More tests --- src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs | 2 -- src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs index 34ab8c899486..bb2e33a4cbbb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs @@ -308,8 +308,6 @@ public void Complete() return; } - _log.ConnectionStop("TEST123 " + Environment.StackTrace); - _completed = true; _outputWriter.Complete(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 7a8836eb4eb6..fc774140e6b7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -393,7 +393,6 @@ public async Task ProcessRequestAsync(IHttpApplication appli private void OnEndStreamReceived() { - Log.LogTrace("OnEndStreamReceived"); if (InputRemaining.HasValue) { // https://tools.ietf.org/html/rfc7540#section-8.1.2.6 From 081468f9ea93b32bee82ce9073cc45b623c82600 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 18 Feb 2020 11:02:39 -0800 Subject: [PATCH 15/27] compiling --- .../Kestrel/Core/src/Internal/Http3/Http3Stream.cs | 3 +-- .../src/Internal/QuicConnectionContext.cs | 7 +++++-- .../Kestrel/samples/Http3SampleApp/Program.cs | 12 +----------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index fc774140e6b7..7dcf2faa3969 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -355,12 +355,11 @@ public async Task ProcessRequestAsync(IHttpApplication appli error = ex; //errorCode = ex.ErrorCode; Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode); - Log.LogWarning(0, ex, "Stream threw an exception."); } catch (Exception ex) { error = ex; - Log.LogWarning(0, ex, "Stream threw an exception."); + Log.LogWarning(0, ex, "Stream threw an unexpected exception."); } finally { diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index 4d36d058868a..ec67345f4622 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal class QuicConnectionContext : TransportMultiplexedConnection + internal class QuicConnectionContext : TransportMultiplexedConnection, IProtocolErrorCodeFeature { private QuicConnection _connection; private readonly QuicTransportContext _context; @@ -20,12 +20,15 @@ internal class QuicConnectionContext : TransportMultiplexedConnection private ValueTask _closeTask; + public long Error { get; set; } + public QuicConnectionContext(QuicConnection connection, QuicTransportContext context) { _log = context.Log; _context = context; _connection = connection; Features.Set(new FakeTlsConnectionFeature()); + Features.Set(this); _log.NewConnection(ConnectionId); } @@ -66,7 +69,7 @@ public override async ValueTask DisposeAsync() public override void Abort(ConnectionAbortedException abortReason) { - _closeTask = _connection.CloseAsync(errorCode: _context.Options.AbortErrorCode); + _closeTask = _connection.CloseAsync(errorCode: Error); } public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index f3229a0da787..7e37138aaf82 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -14,7 +14,7 @@ public class Program { public static void Main(string[] args) { - var cert = CertificateLoader.LoadFromStoreCert("JUSTIN-LAPTOP", StoreName.My.ToString(), StoreLocation.CurrentUser, false); + var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false); var hostBuilder = new HostBuilder() .ConfigureLogging((_, factory) => @@ -44,16 +44,6 @@ public static void Main(string[] args) }); listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); - //options.Listen(IPAddress.Any, basePort, listenOptions => - //{ - // listenOptions.UseHttps(httpsOptions => - // { - // httpsOptions.ServerCertificate = cert; - // }); - // listenOptions.Protocols = HttpProtocols.Http3; - // //listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; - //}); - }) .UseStartup(); }); From 9c6a52759fb0fa7c3fade911ff22e71d086cd914 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 18 Feb 2020 11:51:48 -0800 Subject: [PATCH 16/27] refs --- ...ore.Connections.Abstractions.netcoreapp.cs | 71 +++++++++++++++---- ...Connections.Abstractions.netstandard2.0.cs | 71 +++++++++++++++---- ...Connections.Abstractions.netstandard2.1.cs | 71 +++++++++++++++---- .../src/IConnectionListener.cs | 12 ++-- ...pNetCore.Server.Kestrel.Core.netcoreapp.cs | 5 +- .../Internal/Http/HttpHeaders.Generated.cs | 2 +- .../src/Internal/Http3/Http3Connection.cs | 37 +--------- 7 files changed, 181 insertions(+), 88 deletions(-) diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs index b454acd516b6..9e46e6eff3b1 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs @@ -123,8 +123,58 @@ public partial interface IConnectionListenerFactory { System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } - public partial interface IMultiplexedConnectionListenerFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory + public partial interface IMultiplexedConnectionBuilder { + System.IServiceProvider ApplicationServices { get; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware); + } + public partial interface IMultiplexedConnectionFactory + { + System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable + { + System.Net.EndPoint EndPoint { get; } + System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListenerFactory + { + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder + { + public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } + } + public static partial class MultiplexedConnectionBuilderExtensions + { + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } + } + public abstract partial class MultiplexedConnectionContext : System.IAsyncDisposable + { + protected MultiplexedConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual void Abort() { } + public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } + public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); + public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext + { + protected StreamContext() { } + public abstract long StreamId { get; } } [System.FlagsAttribute] public enum TransferFormat @@ -139,14 +189,6 @@ public UriEndPoint(System.Uri uri) { } public override string ToString() { throw null; } } } -namespace Microsoft.AspNetCore.Connections.Abstractions.Features -{ - public partial interface IQuicCreateStreamFeature - { - System.Threading.Tasks.ValueTask StartBidirectionalStreamAsync(); - System.Threading.Tasks.ValueTask StartUnidirectionalStreamAsync(); - } -} namespace Microsoft.AspNetCore.Connections.Features { public partial interface IConnectionCompleteFeature @@ -196,15 +238,14 @@ public partial interface IMemoryPoolFeature { System.Buffers.MemoryPool MemoryPool { get; } } - public partial interface IQuicStreamFeature + public partial interface IProtocolErrorCodeFeature { - bool CanRead { get; } - bool CanWrite { get; } - long StreamId { get; } + long Error { get; set; } } - public partial interface IQuicStreamListenerFeature + public partial interface IStreamDirectionFeature { - System.Threading.Tasks.ValueTask AcceptAsync(); + bool CanRead { get; } + bool CanWrite { get; } } public partial interface ITlsHandshakeFeature { diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index b454acd516b6..9e46e6eff3b1 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -123,8 +123,58 @@ public partial interface IConnectionListenerFactory { System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } - public partial interface IMultiplexedConnectionListenerFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory + public partial interface IMultiplexedConnectionBuilder { + System.IServiceProvider ApplicationServices { get; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware); + } + public partial interface IMultiplexedConnectionFactory + { + System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable + { + System.Net.EndPoint EndPoint { get; } + System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListenerFactory + { + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder + { + public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } + } + public static partial class MultiplexedConnectionBuilderExtensions + { + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } + } + public abstract partial class MultiplexedConnectionContext : System.IAsyncDisposable + { + protected MultiplexedConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual void Abort() { } + public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } + public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); + public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext + { + protected StreamContext() { } + public abstract long StreamId { get; } } [System.FlagsAttribute] public enum TransferFormat @@ -139,14 +189,6 @@ public UriEndPoint(System.Uri uri) { } public override string ToString() { throw null; } } } -namespace Microsoft.AspNetCore.Connections.Abstractions.Features -{ - public partial interface IQuicCreateStreamFeature - { - System.Threading.Tasks.ValueTask StartBidirectionalStreamAsync(); - System.Threading.Tasks.ValueTask StartUnidirectionalStreamAsync(); - } -} namespace Microsoft.AspNetCore.Connections.Features { public partial interface IConnectionCompleteFeature @@ -196,15 +238,14 @@ public partial interface IMemoryPoolFeature { System.Buffers.MemoryPool MemoryPool { get; } } - public partial interface IQuicStreamFeature + public partial interface IProtocolErrorCodeFeature { - bool CanRead { get; } - bool CanWrite { get; } - long StreamId { get; } + long Error { get; set; } } - public partial interface IQuicStreamListenerFeature + public partial interface IStreamDirectionFeature { - System.Threading.Tasks.ValueTask AcceptAsync(); + bool CanRead { get; } + bool CanWrite { get; } } public partial interface ITlsHandshakeFeature { diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs index b454acd516b6..9e46e6eff3b1 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs @@ -123,8 +123,58 @@ public partial interface IConnectionListenerFactory { System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } - public partial interface IMultiplexedConnectionListenerFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory + public partial interface IMultiplexedConnectionBuilder { + System.IServiceProvider ApplicationServices { get; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware); + } + public partial interface IMultiplexedConnectionFactory + { + System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable + { + System.Net.EndPoint EndPoint { get; } + System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IMultiplexedConnectionListenerFactory + { + System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder + { + public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } + public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } + } + public static partial class MultiplexedConnectionBuilderExtensions + { + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } + } + public abstract partial class MultiplexedConnectionContext : System.IAsyncDisposable + { + protected MultiplexedConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual void Abort() { } + public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } + public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); + public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext + { + protected StreamContext() { } + public abstract long StreamId { get; } } [System.FlagsAttribute] public enum TransferFormat @@ -139,14 +189,6 @@ public UriEndPoint(System.Uri uri) { } public override string ToString() { throw null; } } } -namespace Microsoft.AspNetCore.Connections.Abstractions.Features -{ - public partial interface IQuicCreateStreamFeature - { - System.Threading.Tasks.ValueTask StartBidirectionalStreamAsync(); - System.Threading.Tasks.ValueTask StartUnidirectionalStreamAsync(); - } -} namespace Microsoft.AspNetCore.Connections.Features { public partial interface IConnectionCompleteFeature @@ -196,15 +238,14 @@ public partial interface IMemoryPoolFeature { System.Buffers.MemoryPool MemoryPool { get; } } - public partial interface IQuicStreamFeature + public partial interface IProtocolErrorCodeFeature { - bool CanRead { get; } - bool CanWrite { get; } - long StreamId { get; } + long Error { get; set; } } - public partial interface IQuicStreamListenerFeature + public partial interface IStreamDirectionFeature { - System.Threading.Tasks.ValueTask AcceptAsync(); + bool CanRead { get; } + bool CanWrite { get; } } public partial interface ITlsHandshakeFeature { diff --git a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs index 8295c9f93d46..464a8650f9b7 100644 --- a/src/Servers/Connections.Abstractions/src/IConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IConnectionListener.cs @@ -19,17 +19,17 @@ public interface IConnectionListener : IAsyncDisposable EndPoint EndPoint { get; } /// - /// Stops listening for incoming connections. + /// Begins an asynchronous operation to accept an incoming connection. /// /// The token to monitor for cancellation requests. - /// A that represents the un-bind operation. - ValueTask UnbindAsync(CancellationToken cancellationToken = default); + /// A that completes when a connection is accepted, yielding the representing the connection. + ValueTask AcceptAsync(CancellationToken cancellationToken = default); /// - /// Begins an asynchronous operation to accept an incoming connection. + /// Stops listening for incoming connections. /// /// The token to monitor for cancellation requests. - /// A that completes when a connection is accepted, yielding the representing the connection. - ValueTask AcceptAsync(CancellationToken cancellationToken = default); + /// A that represents the un-bind operation. + ValueTask UnbindAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs index 4bc68180469b..4874e2ca5b58 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs @@ -96,6 +96,7 @@ public enum HttpProtocols public partial class KestrelServer : Microsoft.AspNetCore.Hosting.Server.IServer, System.IDisposable { public KestrelServer(Microsoft.Extensions.Options.IOptions options, System.Collections.Generic.IEnumerable transportFactories, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public KestrelServer(Microsoft.Extensions.Options.IOptions options, System.Collections.Generic.IEnumerable transportFactories, System.Collections.Generic.IEnumerable multiplexedFactories, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions Options { get { throw null; } } public void Dispose() { } @@ -149,7 +150,7 @@ public void ListenLocalhost(int port, System.Action configure) { } } - public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder + public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectionBuilder, Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder { internal ListenOptions() { } public System.IServiceProvider ApplicationServices { get { throw null; } } @@ -159,8 +160,10 @@ internal ListenOptions() { } public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public string SocketPath { get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } public override string ToString() { throw null; } public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware) { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } } public partial class MinDataRate { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs index ff6926ef336a..c46e2183c5a8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -12201,4 +12201,4 @@ public bool MoveNext() } } } -} +} \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 3a51915c656d..8d4631f0ff18 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -115,15 +115,7 @@ public void StopProcessingNextRequest() previousState = _aborted; } - if (!previousState) - { - //var initiator = serverInitiated ? GracefulCloseInitiator.Server : GracefulCloseInitiator.Client; - - //if (Interlocked.CompareExchange(ref _gracefulCloseInitiator, initiator, GracefulCloseInitiator.None) == GracefulCloseInitiator.None) - //{ - // Input.CancelPendingRead(); - //} - } + // TODO figure out how to gracefully close next requests } public void OnConnectionClosed() @@ -134,34 +126,9 @@ public void OnConnectionClosed() previousState = _aborted; } - if (!previousState) - { - //TryClose(); - //_frameWriter.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient)); - } + // TODO figure out how to gracefully close next requests } - //private bool TryClose() - //{ - // if (Interlocked.Exchange(ref _isClosed, 1) == 0) - // { - // Log.Http2ConnectionClosed(_context.ConnectionId, _highestOpenedStreamId); - // return true; - // } - - // return false; - //} - - //public void Abort(ConnectionAbortedException ex) - //{ - // if (TryClose()) - // { - // _frameWriter.WriteGoAwayAsync(int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - // } - - // _frameWriter.Abort(ex); - //} - public void Abort(ConnectionAbortedException ex) { bool previousState; From 8bbd75e9e2b94f9724cdd53a5b43cf2e0ec10138 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 19 Feb 2020 09:20:35 -0800 Subject: [PATCH 17/27] feedback --- .../Web.JS/dist/Release/blazor.server.js | 2 +- .../src/IMultiplexedConnectionBuilder.cs | 4 ++-- .../src/MultiplexedConnectionBuilder.cs | 4 ++-- .../src/MultiplexedConnectionBuilderExtensions.cs | 8 ++++---- .../src/MultiplexedConnectionContext.cs | 14 +------------- .../Middleware/HttpConnectionBuilderExtensions.cs | 2 +- .../Kestrel/samples/QuicSampleApp/Program.cs | 2 +- .../Http3/Http3StreamTests.cs | 1 - 8 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index e04bab52cc11..b6d853ce324e 100644 --- a/src/Components/Web.JS/dist/Release/blazor.server.js +++ b/src/Components/Web.JS/dist/Release/blazor.server.js @@ -12,4 +12,4 @@ var r=n(50),o=n(51),i=n(52);function a(){return c.TYPED_ARRAY_SUPPORT?2147483647 * @author Feross Aboukhadijeh * @license MIT */ -function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(9))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(10)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(15).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(9))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(10),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(21);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(16),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(34).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(11),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t=o().encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t=o().encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o().encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o().encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}();n.d(t,"VERSION",function(){return u}),n.d(t,"MessagePackHubProtocol",function(){return c});var u="5.0.0-dev"}]); \ No newline at end of file +function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(9))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(10)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(15).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(9))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(10),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(21);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(16),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(34).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(11),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t=o().encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t=o().encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o().encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o().encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}();n.d(t,"VERSION",function(){return u}),n.d(t,"MessagePackHubProtocol",function(){return c});var u="0.0.0-DEV_BUILD"}]); \ No newline at end of file diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs index 10eef0042296..8f3caf34db54 100644 --- a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs @@ -20,12 +20,12 @@ public interface IMultiplexedConnectionBuilder /// /// The middleware delegate. /// The . - IMultiplexedConnectionBuilder UseMultiplexed(Func middleware); + IMultiplexedConnectionBuilder Use(Func middleware); /// /// Builds the delegate used by this application to process connections. /// /// The connection handling delegate. - MultiplexedConnectionDelegate BuildMultiplexed(); + MultiplexedConnectionDelegate Build(); } } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs index fba04e05af4f..202f29df5eb5 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs @@ -19,13 +19,13 @@ public MultiplexedConnectionBuilder(IServiceProvider applicationServices) ApplicationServices = applicationServices; } - public IMultiplexedConnectionBuilder UseMultiplexed(Func middleware) + public IMultiplexedConnectionBuilder Use(Func middleware) { _components.Add(middleware); return this; } - public MultiplexedConnectionDelegate BuildMultiplexed() + public MultiplexedConnectionDelegate Build() { MultiplexedConnectionDelegate app = features => { diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs index 25492eab5e43..43f84887b8dd 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs @@ -8,9 +8,9 @@ namespace Microsoft.AspNetCore.Connections { public static class MultiplexedConnectionBuilderExtensions { - public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func, Task> middleware) + public static IMultiplexedConnectionBuilder Use(this IMultiplexedConnectionBuilder connectionBuilder, Func, Task> middleware) { - return connectionBuilder.UseMultiplexed(next => + return connectionBuilder.Use(next => { return context => { @@ -20,9 +20,9 @@ public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConn }); } - public static IMultiplexedConnectionBuilder RunMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) + public static IMultiplexedConnectionBuilder Run(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) { - return connectionBuilder.UseMultiplexed(next => + return connectionBuilder.Use(next => { return context => { diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs index 078b179b3bb9..68b34a48ce12 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -70,18 +70,6 @@ public virtual ValueTask DisposeAsync() /// /// Aborts the underlying connection. /// - /// An optional describing the reason the connection is being terminated. - public virtual void Abort(ConnectionAbortedException abortReason) - { - // We expect this to be overridden, but this helps maintain back compat - // with implementations of ConnectionContext that predate the addition of - // ConnectionContext.Abort() - Features.Get()?.Abort(); - } - - /// - /// Aborts the underlying connection. - /// - public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via MultiplexedConnectionContext.Abort().")); + public abstract void Abort(); } } diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs index 33d8356db073..09c1ce476cfa 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs @@ -20,7 +20,7 @@ public static IConnectionBuilder UseHttpServer(this IConnectionBuilder public static IMultiplexedConnectionBuilder UseHttp3Server(this IMultiplexedConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { var middleware = new Http3ConnectionMiddleware(serviceContext, application); - return builder.UseMultiplexed(next => + return builder.Use(next => { return middleware.OnConnectionAsync; }); diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index f267bb8c510f..8bd8fd681d67 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -66,7 +66,7 @@ async Task EchoServer(MultiplexedConnectionContext connection) } } - listenOptions.RunMultiplexed(EchoServer); + listenOptions.Run(EchoServer); }); }) .UseStartup(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index b255842a3af7..860caa46c08b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -584,7 +584,6 @@ public async Task ContentLength_Received_MultipleDataFrame_ReadViaPipe_Verified( Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]); } - [Fact(Skip = "Http3OutputProducer.Complete is called before input recognizes there is an error. Why is this different than HTTP/2?")] public async Task ContentLength_Received_NoDataFrames_Reset() { From 30dbdfb75f3ba599a75c69c5254045dfc4fc754e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 19 Feb 2020 10:18:02 -0800 Subject: [PATCH 18/27] Reverting feedback --- .../src/BaseConnectionContext.cs | 64 +++++++++++++++++++ .../src/ConnectionContext.cs | 50 +-------------- .../src/IMultiplexedConnectionBuilder.cs | 4 +- .../src/MultiplexedConnectionBuilder.cs | 4 +- .../MultiplexedConnectionBuilderExtensions.cs | 8 +-- .../src/MultiplexedConnectionContext.cs | 49 +------------- .../src/Internal/Http3/Http3Connection.cs | 4 +- .../src/Internal/Http3ConnectionContext.cs | 2 +- .../Middleware/Http3ConnectionMiddleware.cs | 2 +- .../HttpConnectionBuilderExtensions.cs | 2 +- .../src/Internal/QuicConnectionContext.cs | 2 + .../Kestrel/samples/QuicSampleApp/Program.cs | 2 +- .../Http3/Http3TestBase.cs | 10 ++- 13 files changed, 93 insertions(+), 110 deletions(-) create mode 100644 src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs diff --git a/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs b/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs new file mode 100644 index 000000000000..1c0e61436957 --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Connections +{ + public abstract class BaseConnectionContext + { + /// + /// Gets or sets a unique identifier to represent this connection in trace logs. + /// + public abstract string ConnectionId { get; set; } + + /// + /// Gets the collection of features provided by the server and middleware available on this connection. + /// + public abstract IFeatureCollection Features { get; } + + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this connection. + /// + public abstract IDictionary Items { get; set; } + + /// + /// Triggered when the client connection is closed. + /// + public virtual CancellationToken ConnectionClosed { get; set; } + + /// + /// Gets or sets the local endpoint for this connection. + /// + public virtual EndPoint LocalEndPoint { get; set; } + + /// + /// Gets or sets the remote endpoint for this connection. + /// + public virtual EndPoint RemoteEndPoint { get; set; } + + /// + /// Aborts the underlying connection. + /// + public abstract void Abort(); + + /// + /// Aborts the underlying connection. + /// + /// An optional describing the reason the connection is being terminated. + public abstract void Abort(ConnectionAbortedException abortReason); + + /// + /// Releases resources for the underlying connection. + /// + /// A that completes when resources have been released. + public virtual ValueTask DisposeAsync() + { + return default; + } + } +} diff --git a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs index 947066ca796f..02b291c2c816 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionContext.cs @@ -2,61 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO.Pipelines; -using System.Net; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { /// /// Encapsulates all information about an individual connection. /// - public abstract class ConnectionContext : IAsyncDisposable + public abstract class ConnectionContext : BaseConnectionContext, IAsyncDisposable { - /// - /// Gets or sets a unique identifier to represent this connection in trace logs. - /// - public abstract string ConnectionId { get; set; } - - /// - /// Gets the collection of features provided by the server and middleware available on this connection. - /// - public abstract IFeatureCollection Features { get; } - - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this connection. - /// - public abstract IDictionary Items { get; set; } - /// /// Gets or sets the that can be used to read or write data on this connection. /// public abstract IDuplexPipe Transport { get; set; } - /// - /// Triggered when the client connection is closed. - /// - public virtual CancellationToken ConnectionClosed { get; set; } - - /// - /// Gets or sets the local endpoint for this connection. - /// - public virtual EndPoint LocalEndPoint { get; set; } - - /// - /// Gets or sets the remote endpoint for this connection. - /// - public virtual EndPoint RemoteEndPoint { get; set; } - /// /// Aborts the underlying connection. /// /// An optional describing the reason the connection is being terminated. - public virtual void Abort(ConnectionAbortedException abortReason) + public override void Abort(ConnectionAbortedException abortReason) { // We expect this to be overridden, but this helps maintain back compat // with implementations of ConnectionContext that predate the addition of @@ -67,15 +32,6 @@ public virtual void Abort(ConnectionAbortedException abortReason) /// /// Aborts the underlying connection. /// - public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); - - /// - /// Releases resources for the underlying connection. - /// - /// A that completes when resources have been released. - public virtual ValueTask DisposeAsync() - { - return default; - } + public override void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); } } diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs index 8f3caf34db54..10eef0042296 100644 --- a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs @@ -20,12 +20,12 @@ public interface IMultiplexedConnectionBuilder /// /// The middleware delegate. /// The . - IMultiplexedConnectionBuilder Use(Func middleware); + IMultiplexedConnectionBuilder UseMultiplexed(Func middleware); /// /// Builds the delegate used by this application to process connections. /// /// The connection handling delegate. - MultiplexedConnectionDelegate Build(); + MultiplexedConnectionDelegate BuildMultiplexed(); } } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs index 202f29df5eb5..fba04e05af4f 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs @@ -19,13 +19,13 @@ public MultiplexedConnectionBuilder(IServiceProvider applicationServices) ApplicationServices = applicationServices; } - public IMultiplexedConnectionBuilder Use(Func middleware) + public IMultiplexedConnectionBuilder UseMultiplexed(Func middleware) { _components.Add(middleware); return this; } - public MultiplexedConnectionDelegate Build() + public MultiplexedConnectionDelegate BuildMultiplexed() { MultiplexedConnectionDelegate app = features => { diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs index 43f84887b8dd..25492eab5e43 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs @@ -8,9 +8,9 @@ namespace Microsoft.AspNetCore.Connections { public static class MultiplexedConnectionBuilderExtensions { - public static IMultiplexedConnectionBuilder Use(this IMultiplexedConnectionBuilder connectionBuilder, Func, Task> middleware) + public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func, Task> middleware) { - return connectionBuilder.Use(next => + return connectionBuilder.UseMultiplexed(next => { return context => { @@ -20,9 +20,9 @@ public static IMultiplexedConnectionBuilder Use(this IMultiplexedConnectionBuild }); } - public static IMultiplexedConnectionBuilder Run(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) + public static IMultiplexedConnectionBuilder RunMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) { - return connectionBuilder.Use(next => + return connectionBuilder.UseMultiplexed(next => { return context => { diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs index 68b34a48ce12..350eabb6835a 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -2,56 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Net; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { - public abstract class MultiplexedConnectionContext : IAsyncDisposable + public abstract class MultiplexedConnectionContext : BaseConnectionContext, IAsyncDisposable { - /// - /// Gets or sets a unique identifier to represent this connection in trace logs. - /// - public abstract string ConnectionId { get; set; } - - /// - /// Gets the collection of features provided by the server and middleware available on this connection. - /// - public abstract IFeatureCollection Features { get; } - - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this connection. - /// - public abstract IDictionary Items { get; set; } - - /// - /// Triggered when the client connection is closed. - /// - public virtual CancellationToken ConnectionClosed { get; set; } - - /// - /// Gets or sets the local endpoint for this connection. - /// - public virtual EndPoint LocalEndPoint { get; set; } - - /// - /// Gets or sets the remote endpoint for this connection. - /// - public virtual EndPoint RemoteEndPoint { get; set; } - - /// - /// Releases resources for the underlying connection. - /// - /// A that completes when resources have been released. - public virtual ValueTask DisposeAsync() - { - return default; - } - /// /// Asynchronously accept an incoming stream on the connection. /// @@ -66,10 +24,5 @@ public virtual ValueTask DisposeAsync() /// /// public abstract ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); - - /// - /// Aborts the underlying connection. - /// - public abstract void Abort(); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 8d4631f0ff18..91c2d634ddd0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -40,7 +40,7 @@ internal class Http3Connection : IRequestProcessor, ITimeoutHandler public Http3Connection(Http3ConnectionContext context) { - _multiplexedContext = context.MultiplexedConnectionContext; + _multiplexedContext = context.ConnectionContext; _context = context; DynamicTable = new DynamicTable(0); _systemClock = context.ServiceContext.SystemClock; @@ -89,7 +89,7 @@ public async Task ProcessRequestsAsync(IHttpApplication http using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((Http3Connection)state).StopProcessingNextRequest(), this); // Register for connection close - using var closedRegistration = _context.MultiplexedConnectionContext.ConnectionClosed.Register(state => ((Http3Connection)state).OnConnectionClosed(), this); + using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((Http3Connection)state).OnConnectionClosed(), this); await InnerProcessRequestsAsync(httpApplication); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs index 42bf2cb6c165..e20f8c33a6f5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3ConnectionContext.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal internal class Http3ConnectionContext { public string ConnectionId { get; set; } - public MultiplexedConnectionContext MultiplexedConnectionContext { get; set; } + public MultiplexedConnectionContext ConnectionContext { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } public MemoryPool MemoryPool { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs index ec5e9b000392..c330a69ca660 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Http3ConnectionMiddleware.cs @@ -28,7 +28,7 @@ public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) var http3ConnectionContext = new Http3ConnectionContext { ConnectionId = connectionContext.ConnectionId, - MultiplexedConnectionContext = connectionContext, + ConnectionContext = connectionContext, ServiceContext = _serviceContext, ConnectionFeatures = connectionContext.Features, MemoryPool = memoryPoolFeature.MemoryPool, diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs index 09c1ce476cfa..33d8356db073 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs @@ -20,7 +20,7 @@ public static IConnectionBuilder UseHttpServer(this IConnectionBuilder public static IMultiplexedConnectionBuilder UseHttp3Server(this IMultiplexedConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { var middleware = new Http3ConnectionMiddleware(serviceContext, application); - return builder.Use(next => + return builder.UseMultiplexed(next => { return middleware.OnConnectionAsync; }); diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index ec67345f4622..ef3cea228642 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -67,6 +67,8 @@ public override async ValueTask DisposeAsync() _connection.Dispose(); } + public override void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via MultiplexedConnectionContext.Abort().")); + public override void Abort(ConnectionAbortedException abortReason) { _closeTask = _connection.CloseAsync(errorCode: Error); diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index 8bd8fd681d67..f267bb8c510f 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -66,7 +66,7 @@ async Task EchoServer(MultiplexedConnectionContext connection) } } - listenOptions.Run(EchoServer); + listenOptions.RunMultiplexed(EchoServer); }); }) .UseStartup(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index fb52bc77f9db..328d0d695c73 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -133,7 +133,7 @@ protected void CreateConnection() var httpConnectionContext = new Http3ConnectionContext { - MultiplexedConnectionContext = _multiplexedContext, + ConnectionContext = _multiplexedContext, ConnectionFeatures = features, ServiceContext = _serviceContext, MemoryPool = _memoryPool, @@ -450,6 +450,14 @@ public TestMultiplexedConnectionContext(Http3TestBase testBase) public override IDictionary Items { get; set; } + public override void Abort() + { + } + + public override void Abort(ConnectionAbortedException abortReason) + { + } + public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { while (await AcceptQueue.Reader.WaitToReadAsync()) From f3c41d99f1ce8e5e6b3cd3b646e0bb4480077816 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 19 Feb 2020 16:12:39 -0800 Subject: [PATCH 19/27] Last feedback --- .../Core/src/Internal/ConnectionDispatcher.cs | 2 +- .../Infrastructure/ConnectionManager.cs | 2 +- .../Infrastructure/ConnectionReference.cs | 4 +- .../Infrastructure/KestrelConnection.cs | 63 +---- .../Infrastructure/KestrelConnectionOfT.cs | 77 ++++++ .../Infrastructure/KestrelEventSource.cs | 26 +- .../MultiplexedConnectionManager.cs | 110 --------- .../MultiplexedConnectionReference.cs | 25 -- .../MultiplexedHeartbeatManager.cs | 41 ---- .../MultiplexedKestrelConnection.cs | 230 ------------------ .../MultiplexedConnectionDispatcher.cs | 4 +- .../Core/src/Internal/ServiceContext.cs | 2 - src/Servers/Kestrel/Core/src/KestrelServer.cs | 7 +- .../Core/test/ConnectionDispatcherTests.cs | 10 +- .../Core/test/HttpConnectionManagerTests.cs | 2 +- 15 files changed, 99 insertions(+), 506 deletions(-) create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index df08a8d320d9..a373c240bf8d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -53,7 +53,7 @@ async Task AcceptConnectionsAsync() // Add the connection to the connection manager before we queue it for execution var id = Interlocked.Increment(ref _lastConnectionId); - var kestrelConnection = new KestrelConnection(id, _serviceContext, _connectionDelegate, connection, Log); + var kestrelConnection = new KestrelConnection(id, _serviceContext, c => _connectionDelegate(c), connection, Log); _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs index 05bb0f0726a5..eb20cbac192b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs @@ -93,7 +93,7 @@ public async Task AbortAllConnectionsAsync() Walk(connection => { - connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); + connection.GetTransport().Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); abortTasks.Add(connection.ExecutionTask); }); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs index b5dc202e01bd..5e1702b7a668 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionReference.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -12,7 +12,7 @@ internal class ConnectionReference public ConnectionReference(KestrelConnection connection) { _weakReference = new WeakReference(connection); - ConnectionId = connection.TransportConnection.ConnectionId; + ConnectionId = connection.GetTransport().ConnectionId; } public string ConnectionId { get; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs index 67a6b52d305c..ecb09f1784cd 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnection.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { - internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature, IThreadPoolWorkItem + internal abstract class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature { private List<(Action handler, object state)> _heartbeatHandlers; private readonly object _heartbeatLock = new object(); @@ -21,31 +21,22 @@ internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompl private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); private readonly TaskCompletionSource _completionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private readonly long _id; - private readonly ServiceContext _serviceContext; - private readonly ConnectionDelegate _connectionDelegate; + protected readonly long _id; + protected readonly ServiceContext _serviceContext; public KestrelConnection(long id, ServiceContext serviceContext, - ConnectionDelegate connectionDelegate, - ConnectionContext connectionContext, IKestrelTrace logger) { _id = id; _serviceContext = serviceContext; - _connectionDelegate = connectionDelegate; Logger = logger; - TransportConnection = connectionContext; - connectionContext.Features.Set(this); - connectionContext.Features.Set(this); - connectionContext.Features.Set(this); ConnectionClosedRequested = _connectionClosingCts.Token; } - private IKestrelTrace Logger { get; } + protected IKestrelTrace Logger { get; } - public ConnectionContext TransportConnection { get; set; } public CancellationToken ConnectionClosedRequested { get; set; } public Task ExecutionTask => _completionTcs.Task; @@ -65,6 +56,8 @@ public void TickHeartbeat() } } + public abstract BaseConnectionContext GetTransport(); + public void OnHeartbeat(Action action, object state) { lock (_heartbeatLock) @@ -175,49 +168,7 @@ public void Complete() _connectionClosingCts.Dispose(); } - void IThreadPoolWorkItem.Execute() - { - _ = ExecuteAsync(); - } - - internal async Task ExecuteAsync() - { - var connectionContext = TransportConnection; - - try - { - Logger.ConnectionStart(connectionContext.ConnectionId); - KestrelEventSource.Log.ConnectionStart(connectionContext); - - using (BeginConnectionScope(connectionContext)) - { - try - { - await _connectionDelegate(connectionContext); - } - catch (Exception ex) - { - Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); - } - } - } - finally - { - await FireOnCompletedAsync(); - - Logger.ConnectionStop(connectionContext.ConnectionId); - KestrelEventSource.Log.ConnectionStop(connectionContext); - - // Dispose the transport connection, this needs to happen before removing it from the - // connection manager so that we only signal completion of this connection after the transport - // is properly torn down. - await TransportConnection.DisposeAsync(); - - _serviceContext.ConnectionManager.RemoveConnection(_id); - } - } - - private IDisposable BeginConnectionScope(ConnectionContext connectionContext) + protected IDisposable BeginConnectionScope(BaseConnectionContext connectionContext) { if (Logger.IsEnabled(LogLevel.Critical)) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs new file mode 100644 index 000000000000..5da49971779a --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs @@ -0,0 +1,77 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal class KestrelConnection : KestrelConnection, IThreadPoolWorkItem where T : BaseConnectionContext + { + private readonly Func _connectionDelegate; + + public T TransportConnection { get; set; } + + public KestrelConnection(long id, + ServiceContext serviceContext, + Func connectionDelegate, + T connectionContext, + IKestrelTrace logger) + : base(id, serviceContext, logger) + { + _connectionDelegate = connectionDelegate; + TransportConnection = connectionContext; + connectionContext.Features.Set(this); + connectionContext.Features.Set(this); + connectionContext.Features.Set(this); + } + + void IThreadPoolWorkItem.Execute() + { + _ = ExecuteAsync(); + } + + internal async Task ExecuteAsync() + { + var connectionContext = TransportConnection; + + try + { + Logger.ConnectionStart(connectionContext.ConnectionId); + KestrelEventSource.Log.ConnectionStart(connectionContext); + + using (BeginConnectionScope(connectionContext)) + { + try + { + await _connectionDelegate(connectionContext); + } + catch (Exception ex) + { + Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); + } + } + } + finally + { + await FireOnCompletedAsync(); + + Logger.ConnectionStop(connectionContext.ConnectionId); + KestrelEventSource.Log.ConnectionStop(connectionContext); + + // Dispose the transport connection, this needs to happen before removing it from the + // connection manager so that we only signal completion of this connection after the transport + // is properly torn down. + await TransportConnection.DisposeAsync(); + + _serviceContext.ConnectionManager.RemoveConnection(_id); + } + } + + public override BaseConnectionContext GetTransport() + { + return TransportConnection; + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs index 52c76a840184..19afb9af715c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs @@ -26,20 +26,7 @@ private KestrelEventSource() // - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object. [NonEvent] - public void ConnectionStart(ConnectionContext connection) - { - // avoid allocating strings unless this event source is enabled - if (IsEnabled()) - { - ConnectionStart( - connection.ConnectionId, - connection.LocalEndPoint?.ToString(), - connection.RemoteEndPoint?.ToString()); - } - } - - [NonEvent] - public void ConnectionStart(MultiplexedConnectionContext connection) + public void ConnectionStart(BaseConnectionContext connection) { // avoid allocating strings unless this event source is enabled if (IsEnabled()) @@ -66,16 +53,7 @@ private void ConnectionStart(string connectionId, } [NonEvent] - public void ConnectionStop(ConnectionContext connection) - { - if (IsEnabled()) - { - ConnectionStop(connection.ConnectionId); - } - } - - [NonEvent] - public void ConnectionStop(MultiplexedConnectionContext connection) + public void ConnectionStop(BaseConnectionContext connection) { if (IsEnabled()) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs deleted file mode 100644 index 5c981e39caeb..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionManager.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - internal class MultiplexedConnectionManager - { - private readonly ConcurrentDictionary _connectionReferences = new ConcurrentDictionary(); - private readonly IKestrelTrace _trace; - - public MultiplexedConnectionManager(IKestrelTrace trace) - { - _trace = trace; - } - - public void AddConnection(long id, MultiplexedKestrelConnection connection) - { - if (!_connectionReferences.TryAdd(id, new MultiplexedConnectionReference(connection))) - { - throw new ArgumentException(nameof(id)); - } - } - - public void RemoveConnection(long id) - { - if (!_connectionReferences.TryRemove(id, out var reference)) - { - throw new ArgumentException(nameof(id)); - } - - if (reference.TryGetConnection(out var connection)) - { - connection.Complete(); - } - } - - public void Walk(Action callback) - { - foreach (var kvp in _connectionReferences) - { - var reference = kvp.Value; - - if (reference.TryGetConnection(out var connection)) - { - callback(connection); - } - else if (_connectionReferences.TryRemove(kvp.Key, out reference)) - { - // It's safe to modify the ConcurrentDictionary in the foreach. - // The connection reference has become unrooted because the application never completed. - _trace.ApplicationNeverCompleted(reference.ConnectionId); - } - - // If both conditions are false, the connection was removed during the heartbeat. - } - } - - public async Task CloseAllConnectionsAsync(CancellationToken token) - { - var closeTasks = new List(); - - Walk(connection => - { - connection.RequestClose(); - closeTasks.Add(connection.ExecutionTask); - }); - - var allClosedTask = Task.WhenAll(closeTasks.ToArray()); - return await Task.WhenAny(allClosedTask, CancellationTokenAsTask(token)).ConfigureAwait(false) == allClosedTask; - } - - public async Task AbortAllConnectionsAsync() - { - var abortTasks = new List(); - - Walk(connection => - { - connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); - abortTasks.Add(connection.ExecutionTask); - }); - - var allAbortedTask = Task.WhenAll(abortTasks.ToArray()); - return await Task.WhenAny(allAbortedTask, Task.Delay(1000)).ConfigureAwait(false) == allAbortedTask; - } - - private static Task CancellationTokenAsTask(CancellationToken token) - { - if (token.IsCancellationRequested) - { - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - token.Register(() => tcs.SetResult(null)); - return tcs.Task; - } - - private static ResourceCounter GetCounter(long? number) - => number.HasValue - ? ResourceCounter.Quota(number.Value) - : ResourceCounter.Unlimited; - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs deleted file mode 100644 index 92c1782911df..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedConnectionReference.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - internal class MultiplexedConnectionReference - { - private readonly WeakReference _weakReference; - - public MultiplexedConnectionReference(MultiplexedKestrelConnection connection) - { - _weakReference = new WeakReference(connection); - ConnectionId = connection.TransportConnection.ConnectionId; - } - - public string ConnectionId { get; } - - public bool TryGetConnection(out MultiplexedKestrelConnection connection) - { - return _weakReference.TryGetTarget(out connection); - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs deleted file mode 100644 index de4a6d940615..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedHeartbeatManager.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - internal class MultiplexedHeartbeatManager : IHeartbeatHandler, ISystemClock - { - private readonly MultiplexedConnectionManager _connectionManager; - private readonly Action _walkCallback; - private DateTimeOffset _now; - private long _nowTicks; - - public MultiplexedHeartbeatManager(MultiplexedConnectionManager connectionManager) - { - _connectionManager = connectionManager; - _walkCallback = WalkCallback; - } - - public DateTimeOffset UtcNow => new DateTimeOffset(UtcNowTicks, TimeSpan.Zero); - - public long UtcNowTicks => Volatile.Read(ref _nowTicks); - - public DateTimeOffset UtcNowUnsynchronized => _now; - - public void OnHeartbeat(DateTimeOffset now) - { - _now = now; - Volatile.Write(ref _nowTicks, now.Ticks); - - _connectionManager.Walk(_walkCallback); - } - - private void WalkCallback(MultiplexedKestrelConnection connection) - { - connection.TickHeartbeat(); - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs deleted file mode 100644 index cfdd55108665..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/MultiplexedKestrelConnection.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Features; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure -{ - internal class MultiplexedKestrelConnection : IConnectionHeartbeatFeature, IConnectionCompleteFeature, IConnectionLifetimeNotificationFeature, IThreadPoolWorkItem - { - private List<(Action handler, object state)> _heartbeatHandlers; - private readonly object _heartbeatLock = new object(); - - private Stack, object>> _onCompleted; - private bool _completed; - - private readonly CancellationTokenSource _connectionClosingCts = new CancellationTokenSource(); - private readonly TaskCompletionSource _completionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private readonly long _id; - private readonly ServiceContext _serviceContext; - private readonly MultiplexedConnectionDelegate _multiplexedConnectionDelegate; - - public MultiplexedKestrelConnection(long id, - ServiceContext serviceContext, - MultiplexedConnectionDelegate connectionDelegate, - MultiplexedConnectionContext connectionContext, - IKestrelTrace logger) - { - _id = id; - _serviceContext = serviceContext; - _multiplexedConnectionDelegate = connectionDelegate; - Logger = logger; - TransportConnection = connectionContext; - - connectionContext.Features.Set(this); - connectionContext.Features.Set(this); - connectionContext.Features.Set(this); - ConnectionClosedRequested = _connectionClosingCts.Token; - } - - private IKestrelTrace Logger { get; } - - public MultiplexedConnectionContext TransportConnection { get; set; } - public CancellationToken ConnectionClosedRequested { get; set; } - public Task ExecutionTask => _completionTcs.Task; - - public void TickHeartbeat() - { - lock (_heartbeatLock) - { - if (_heartbeatHandlers == null) - { - return; - } - - foreach (var (handler, state) in _heartbeatHandlers) - { - handler(state); - } - } - } - - public void OnHeartbeat(Action action, object state) - { - lock (_heartbeatLock) - { - if (_heartbeatHandlers == null) - { - _heartbeatHandlers = new List<(Action handler, object state)>(); - } - - _heartbeatHandlers.Add((action, state)); - } - } - - void IConnectionCompleteFeature.OnCompleted(Func callback, object state) - { - if (_completed) - { - throw new InvalidOperationException("The connection is already complete."); - } - - if (_onCompleted == null) - { - _onCompleted = new Stack, object>>(); - } - _onCompleted.Push(new KeyValuePair, object>(callback, state)); - } - - public Task FireOnCompletedAsync() - { - if (_completed) - { - throw new InvalidOperationException("The connection is already complete."); - } - - _completed = true; - var onCompleted = _onCompleted; - - if (onCompleted == null || onCompleted.Count == 0) - { - return Task.CompletedTask; - } - - return CompleteAsyncMayAwait(onCompleted); - } - - private Task CompleteAsyncMayAwait(Stack, object>> onCompleted) - { - while (onCompleted.TryPop(out var entry)) - { - try - { - var task = entry.Key.Invoke(entry.Value); - if (!task.IsCompletedSuccessfully) - { - return CompleteAsyncAwaited(task, onCompleted); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); - } - } - - return Task.CompletedTask; - } - - private async Task CompleteAsyncAwaited(Task currentTask, Stack, object>> onCompleted) - { - try - { - await currentTask; - } - catch (Exception ex) - { - Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); - } - - while (onCompleted.TryPop(out var entry)) - { - try - { - await entry.Key.Invoke(entry.Value); - } - catch (Exception ex) - { - Logger.LogError(ex, "An error occurred running an IConnectionCompleteFeature.OnCompleted callback."); - } - } - } - - public void RequestClose() - { - try - { - _connectionClosingCts.Cancel(); - } - catch (ObjectDisposedException) - { - // There's a race where the token could be disposed - // swallow the exception and no-op - } - } - - public void Complete() - { - _completionTcs.TrySetResult(null); - - _connectionClosingCts.Dispose(); - } - - void IThreadPoolWorkItem.Execute() - { - _ = ExecuteAsync(); - } - - internal async Task ExecuteAsync() - { - var connectionContext = TransportConnection; - - try - { - Logger.ConnectionStart(connectionContext.ConnectionId); - KestrelEventSource.Log.ConnectionStart(connectionContext); - - using (BeginConnectionScope(connectionContext)) - { - try - { - await _multiplexedConnectionDelegate(connectionContext); - } - catch (Exception ex) - { - Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); - } - } - } - finally - { - await FireOnCompletedAsync(); - - Logger.ConnectionStop(connectionContext.ConnectionId); - KestrelEventSource.Log.ConnectionStop(connectionContext); - - // Dispose the transport connection, this needs to happen before removing it from the - // connection manager so that we only signal completion of this connection after the transport - // is properly torn down. - await TransportConnection.DisposeAsync(); - - _serviceContext.ConnectionManager.RemoveConnection(_id); - } - } - - private IDisposable BeginConnectionScope(MultiplexedConnectionContext connectionContext) - { - if (Logger.IsEnabled(LogLevel.Critical)) - { - return Logger.BeginScope(new ConnectionLogScope(connectionContext.ConnectionId)); - } - - return null; - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs index f5c7fcf0413f..61b03c73415a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs @@ -54,9 +54,9 @@ async Task AcceptConnectionsAsync() // Add the connection to the connection manager before we queue it for execution var id = Interlocked.Increment(ref _lastConnectionId); // TODO Don't pass null in here! use a base class - var kestrelConnection = new MultiplexedKestrelConnection(id, _serviceContext, _connectionDelegate, connection, Log); + var kestrelConnection = new KestrelConnection(id, _serviceContext, c => _connectionDelegate(c), connection, Log); - _serviceContext.MultiplexedConnectionManager.AddConnection(id, kestrelConnection); + _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection); Log.ConnectionAccepted(connection.ConnectionId); diff --git a/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs b/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs index 8f9926ded3eb..dae02a6eb070 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs @@ -21,8 +21,6 @@ internal class ServiceContext public ConnectionManager ConnectionManager { get; set; } - public MultiplexedConnectionManager MultiplexedConnectionManager { get; set; } - public Heartbeat Heartbeat { get; set; } public KestrelServerOptions ServerOptions { get; set; } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 78e085ea8ad0..5a3af2a38d72 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -90,15 +90,11 @@ private static ServiceContext CreateServiceContext(IOptions { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var kestrelConnection = new KestrelConnection(0, serviceContext, _ => tcs.Task, connection, serviceContext.Log); + var kestrelConnection = new KestrelConnection(0, serviceContext, _ => tcs.Task, connection, serviceContext.Log); serviceContext.ConnectionManager.AddConnection(0, kestrelConnection); var task = kestrelConnection.ExecuteAsync(); @@ -79,9 +79,9 @@ public async Task OnConnectionFiresOnCompleted() var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); + var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); serviceContext.ConnectionManager.AddConnection(0, kestrelConnection); - var completeFeature = kestrelConnection.TransportConnection.Features.Get(); + var completeFeature = kestrelConnection.GetTransport().Features.Get(); Assert.NotNull(completeFeature); object stateObject = new object(); @@ -100,9 +100,9 @@ public async Task OnConnectionOnCompletedExceptionCaught() var logger = ((TestKestrelTrace)serviceContext.Log).Logger; var connection = new Mock { CallBase = true }.Object; connection.ConnectionClosed = new CancellationToken(canceled: true); - var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); + var kestrelConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, connection, serviceContext.Log); serviceContext.ConnectionManager.AddConnection(0, kestrelConnection); - var completeFeature = kestrelConnection.TransportConnection.Features.Get(); + var completeFeature = kestrelConnection.GetTransport().Features.Get(); Assert.NotNull(completeFeature); object stateObject = new object(); diff --git a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs index 815fe5019a4e..42867902ff0a 100644 --- a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs @@ -44,7 +44,7 @@ private void UnrootedConnectionsGetRemovedFromHeartbeatInnerScope( var serviceContext = new TestServiceContext(); var mock = new Mock() { CallBase = true }; mock.Setup(m => m.ConnectionId).Returns(connectionId); - var httpConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, mock.Object, Mock.Of()); + var httpConnection = new KestrelConnection(0, serviceContext, _ => Task.CompletedTask, mock.Object, Mock.Of()); httpConnectionManager.AddConnection(0, httpConnection); From 4e91bb5544a5f8a675adbb1f830b0dcd822cfa69 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 20 Feb 2020 09:25:02 -0800 Subject: [PATCH 20/27] Deps --- ...ore.Connections.Abstractions.netcoreapp.cs | 45 +++++++++---------- ...Connections.Abstractions.netstandard2.0.cs | 45 +++++++++---------- ...Connections.Abstractions.netstandard2.1.cs | 45 +++++++++---------- .../src/IMultiplexedConnectionBuilder.cs | 4 +- .../src/MultiplexedConnectionBuilder.cs | 4 +- .../MultiplexedConnectionBuilderExtensions.cs | 4 +- ...pNetCore.Server.Kestrel.Core.netcoreapp.cs | 4 +- src/Servers/Kestrel/Core/src/KestrelServer.cs | 2 +- src/Servers/Kestrel/Core/src/ListenOptions.cs | 4 +- .../HttpConnectionBuilderExtensions.cs | 2 +- 10 files changed, 75 insertions(+), 84 deletions(-) diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs index 9e46e6eff3b1..a307b0b8e768 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs @@ -8,6 +8,19 @@ public partial class AddressInUseException : System.InvalidOperationException public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } + public abstract partial class BaseConnectionContext + { + protected BaseConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract void Abort(); + public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public partial class ConnectionAbortedException : System.OperationCanceledException { public ConnectionAbortedException() { } @@ -27,19 +40,12 @@ public static partial class ConnectionBuilderExtensions public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; } } - public abstract partial class ConnectionContext : System.IAsyncDisposable + public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected ConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public override void Abort() { } + public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } } public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection); public abstract partial class ConnectionHandler @@ -126,8 +132,8 @@ public partial interface IConnectionListenerFactory public partial interface IMultiplexedConnectionBuilder { System.IServiceProvider ApplicationServices { get; } - Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed(); - Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware); + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware); } public partial interface IMultiplexedConnectionFactory { @@ -147,28 +153,19 @@ public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connect { public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } - public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } } public static partial class MultiplexedConnectionBuilderExtensions { public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } } - public abstract partial class MultiplexedConnectionContext : System.IAsyncDisposable + public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index 9e46e6eff3b1..a307b0b8e768 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -8,6 +8,19 @@ public partial class AddressInUseException : System.InvalidOperationException public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } + public abstract partial class BaseConnectionContext + { + protected BaseConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract void Abort(); + public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public partial class ConnectionAbortedException : System.OperationCanceledException { public ConnectionAbortedException() { } @@ -27,19 +40,12 @@ public static partial class ConnectionBuilderExtensions public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; } } - public abstract partial class ConnectionContext : System.IAsyncDisposable + public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected ConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public override void Abort() { } + public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } } public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection); public abstract partial class ConnectionHandler @@ -126,8 +132,8 @@ public partial interface IConnectionListenerFactory public partial interface IMultiplexedConnectionBuilder { System.IServiceProvider ApplicationServices { get; } - Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed(); - Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware); + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware); } public partial interface IMultiplexedConnectionFactory { @@ -147,28 +153,19 @@ public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connect { public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } - public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } } public static partial class MultiplexedConnectionBuilderExtensions { public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } } - public abstract partial class MultiplexedConnectionContext : System.IAsyncDisposable + public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs index 9e46e6eff3b1..a307b0b8e768 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs @@ -8,6 +8,19 @@ public partial class AddressInUseException : System.InvalidOperationException public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } + public abstract partial class BaseConnectionContext + { + protected BaseConnectionContext() { } + public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract string ConnectionId { get; set; } + public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } + public abstract System.Collections.Generic.IDictionary Items { get; set; } + public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract void Abort(); + public abstract void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason); + public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public partial class ConnectionAbortedException : System.OperationCanceledException { public ConnectionAbortedException() { } @@ -27,19 +40,12 @@ public static partial class ConnectionBuilderExtensions public static Microsoft.AspNetCore.Connections.IConnectionBuilder Use(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IConnectionBuilder UseConnectionHandler(this Microsoft.AspNetCore.Connections.IConnectionBuilder connectionBuilder) where TConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler { throw null; } } - public abstract partial class ConnectionContext : System.IAsyncDisposable + public abstract partial class ConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected ConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public abstract System.IO.Pipelines.IDuplexPipe Transport { get; set; } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public override void Abort() { } + public override void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } } public delegate System.Threading.Tasks.Task ConnectionDelegate(Microsoft.AspNetCore.Connections.ConnectionContext connection); public abstract partial class ConnectionHandler @@ -126,8 +132,8 @@ public partial interface IConnectionListenerFactory public partial interface IMultiplexedConnectionBuilder { System.IServiceProvider ApplicationServices { get; } - Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed(); - Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware); + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build(); + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware); } public partial interface IMultiplexedConnectionFactory { @@ -147,28 +153,19 @@ public partial class MultiplexedConnectionBuilder : Microsoft.AspNetCore.Connect { public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) { } public System.IServiceProvider ApplicationServices { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } - public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } + public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } + public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } } public static partial class MultiplexedConnectionBuilderExtensions { public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } } - public abstract partial class MultiplexedConnectionContext : System.IAsyncDisposable + public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } - public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract string ConnectionId { get; set; } - public abstract Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get; } - public abstract System.Collections.Generic.IDictionary Items { get; set; } - public virtual System.Net.EndPoint LocalEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual System.Net.EndPoint RemoteEndPoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual void Abort() { } - public virtual void Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { } public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public virtual System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } } public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext diff --git a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs index 10eef0042296..8f3caf34db54 100644 --- a/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/IMultiplexedConnectionBuilder.cs @@ -20,12 +20,12 @@ public interface IMultiplexedConnectionBuilder /// /// The middleware delegate. /// The . - IMultiplexedConnectionBuilder UseMultiplexed(Func middleware); + IMultiplexedConnectionBuilder Use(Func middleware); /// /// Builds the delegate used by this application to process connections. /// /// The connection handling delegate. - MultiplexedConnectionDelegate BuildMultiplexed(); + MultiplexedConnectionDelegate Build(); } } diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs index fba04e05af4f..202f29df5eb5 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilder.cs @@ -19,13 +19,13 @@ public MultiplexedConnectionBuilder(IServiceProvider applicationServices) ApplicationServices = applicationServices; } - public IMultiplexedConnectionBuilder UseMultiplexed(Func middleware) + public IMultiplexedConnectionBuilder Use(Func middleware) { _components.Add(middleware); return this; } - public MultiplexedConnectionDelegate BuildMultiplexed() + public MultiplexedConnectionDelegate Build() { MultiplexedConnectionDelegate app = features => { diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs index 25492eab5e43..e8747a342922 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs @@ -10,7 +10,7 @@ public static class MultiplexedConnectionBuilderExtensions { public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func, Task> middleware) { - return connectionBuilder.UseMultiplexed(next => + return connectionBuilder.Use(next => { return context => { @@ -22,7 +22,7 @@ public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConn public static IMultiplexedConnectionBuilder RunMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) { - return connectionBuilder.UseMultiplexed(next => + return connectionBuilder.Use(next => { return context => { diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs index 4874e2ca5b58..6673afaa1cc1 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs @@ -160,10 +160,10 @@ internal ListenOptions() { } public Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols Protocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public string SocketPath { get { throw null; } } public Microsoft.AspNetCore.Connections.ConnectionDelegate Build() { throw null; } - public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate BuildMultiplexed() { throw null; } + Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder.Build() { throw null; } + Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder.Use(System.Func middleware) { throw null; } public override string ToString() { throw null; } public Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func middleware) { throw null; } - public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(System.Func middleware) { throw null; } } public partial class MinDataRate { diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 5a3af2a38d72..e3c264cadae0 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -154,7 +154,7 @@ async Task OnBind(ListenOptions options) } options.UseHttp3Server(ServiceContext, application, options.Protocols); - var multiplxedConnectionDelegate = options.BuildMultiplexed(); + var multiplxedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build(); var multiplexedConnectionDispatcher = new MultiplexedConnectionDispatcher(ServiceContext, multiplxedConnectionDelegate); var multiplexedFactory = _multiplexedTransportFactories.Last(); diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 3a40b2315e46..16a8e0547775 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -124,7 +124,7 @@ public IConnectionBuilder Use(Func middl return this; } - public IMultiplexedConnectionBuilder UseMultiplexed(Func middleware) + IMultiplexedConnectionBuilder IMultiplexedConnectionBuilder.Use(Func middleware) { _multiplexedMiddleware.Add(middleware); return this; @@ -146,7 +146,7 @@ public ConnectionDelegate Build() return app; } - public MultiplexedConnectionDelegate BuildMultiplexed() + MultiplexedConnectionDelegate IMultiplexedConnectionBuilder.Build() { MultiplexedConnectionDelegate app = context => { diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs index 33d8356db073..09c1ce476cfa 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpConnectionBuilderExtensions.cs @@ -20,7 +20,7 @@ public static IConnectionBuilder UseHttpServer(this IConnectionBuilder public static IMultiplexedConnectionBuilder UseHttp3Server(this IMultiplexedConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { var middleware = new Http3ConnectionMiddleware(serviceContext, application); - return builder.UseMultiplexed(next => + return builder.Use(next => { return middleware.OnConnectionAsync; }); From e65ae2b5cc732b7828f0b1dadda7f210036d2e99 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 20 Feb 2020 09:45:38 -0800 Subject: [PATCH 21/27] Samples --- .../Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs | 2 +- ...rosoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs | 2 +- ...rosoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs | 2 +- .../Connections.Abstractions/src/ConnectionBuilderExtensions.cs | 2 +- .../src/MultiplexedConnectionBuilderExtensions.cs | 2 +- src/Servers/Kestrel/samples/QuicSampleApp/Program.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs index a307b0b8e768..bf277057b1a6 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs @@ -158,7 +158,7 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) } public static partial class MultiplexedConnectionBuilderExtensions { - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Run(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } } public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index a307b0b8e768..bf277057b1a6 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -158,7 +158,7 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) } public static partial class MultiplexedConnectionBuilderExtensions { - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Run(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } } public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs index a307b0b8e768..bf277057b1a6 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs @@ -158,7 +158,7 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) } public static partial class MultiplexedConnectionBuilderExtensions { - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder RunMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } + public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Run(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } } public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable diff --git a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs index 100917b0094a..55b0311eb977 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs @@ -40,4 +40,4 @@ public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder, }); } } -} \ No newline at end of file +} diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs index e8747a342922..c11714d64837 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs @@ -20,7 +20,7 @@ public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConn }); } - public static IMultiplexedConnectionBuilder RunMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) + public static IMultiplexedConnectionBuilder Run(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) { return connectionBuilder.Use(next => { diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index f267bb8c510f..8bd8fd681d67 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -66,7 +66,7 @@ async Task EchoServer(MultiplexedConnectionContext connection) } } - listenOptions.RunMultiplexed(EchoServer); + listenOptions.Run(EchoServer); }); }) .UseStartup(); From 48d6cabd3e4a9f5c8a4912d115b02c47385839f1 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 20 Feb 2020 11:19:52 -0800 Subject: [PATCH 22/27] Fix throwing exception, remove test that isn't valid --- src/Servers/Kestrel/Core/src/KestrelServer.cs | 2 +- .../test/MultplexedConnectionContextTests.cs | 27 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/test/MultplexedConnectionContextTests.cs diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index e3c264cadae0..05030da086be 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -58,7 +58,7 @@ internal KestrelServer(IEnumerable transportFactorie _transportFactories = transportFactories.ToList(); _multiplexedTransportFactories = multiplexedFactories?.ToList(); - if (_transportFactories.Count == 0 && _multiplexedTransportFactories?.Count == 0) + if (_transportFactories.Count == 0 && (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0)) { throw new InvalidOperationException(CoreStrings.TransportNotFound); } diff --git a/src/Servers/Kestrel/Core/test/MultplexedConnectionContextTests.cs b/src/Servers/Kestrel/Core/test/MultplexedConnectionContextTests.cs deleted file mode 100644 index d6d1767a24cd..000000000000 --- a/src/Servers/Kestrel/Core/test/MultplexedConnectionContextTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Connections; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests -{ - public class MultiplexedConnectionContextTests - { - [Fact] - public void ParameterlessAbortCreateConnectionAbortedException() - { - var mockConnectionContext = new Mock { CallBase = true }; - ConnectionAbortedException ex = null; - - mockConnectionContext.Setup(c => c.Abort(It.IsAny())) - .Callback(abortReason => ex = abortReason); - - mockConnectionContext.Object.Abort(); - - Assert.NotNull(ex); - Assert.Equal("The connection was aborted by the application via MultiplexedConnectionContext.Abort().", ex.Message); - } - } -} From 0454beb6c18bcd11767a5eeea5f0d24591941a6c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 20 Feb 2020 14:21:15 -0800 Subject: [PATCH 23/27] Remove extension methods --- .../MultiplexedConnectionBuilderExtensions.cs | 34 ------------------- .../Kestrel/samples/QuicSampleApp/Program.cs | 8 ++++- 2 files changed, 7 insertions(+), 35 deletions(-) delete mode 100644 src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs deleted file mode 100644 index c11714d64837..000000000000 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionBuilderExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Connections -{ - public static class MultiplexedConnectionBuilderExtensions - { - public static IMultiplexedConnectionBuilder UseMultiplexed(this IMultiplexedConnectionBuilder connectionBuilder, Func, Task> middleware) - { - return connectionBuilder.Use(next => - { - return context => - { - Func simpleNext = () => next(context); - return middleware(context, simpleNext); - }; - }); - } - - public static IMultiplexedConnectionBuilder Run(this IMultiplexedConnectionBuilder connectionBuilder, Func middleware) - { - return connectionBuilder.Use(next => - { - return context => - { - return middleware(context); - }; - }); - } - } -} diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index 8bd8fd681d67..54a11c63a054 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -66,7 +66,13 @@ async Task EchoServer(MultiplexedConnectionContext connection) } } - listenOptions.Run(EchoServer); + ((IMultiplexedConnectionBuilder)listenOptions).Use(next => + { + return context => + { + return EchoServer(context); + }; + }); }); }) .UseStartup(); From fd2cae5b655819be1c9b4fedfbbb1833a84eb7e8 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 21 Feb 2020 14:30:03 -0800 Subject: [PATCH 24/27] Feedback --- ...pNetCore.Connections.Abstractions.netcoreapp.cs | 13 ++++++------- ...Core.Connections.Abstractions.netstandard2.0.cs | 13 ++++++------- ...Core.Connections.Abstractions.netstandard2.1.cs | 13 ++++++------- .../src/Features/IStreamIdFeature.cs | 10 ++++++++++ .../src/IMulitplexedConnectionListener.cs | 4 +++- .../Connections.Abstractions/src/StreamContext.cs | 2 +- .../Core/src/Internal/Http3/Http3Connection.cs | 11 ++++++----- .../src/Internal/QuicConnectionListener.cs | 3 ++- .../src/Internal/QuicStreamContext.cs | 12 ++++++++++-- .../Http3/Http3TestBase.cs | 14 +++++++++++--- 10 files changed, 61 insertions(+), 34 deletions(-) create mode 100644 src/Servers/Connections.Abstractions/src/Features/IStreamIdFeature.cs diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs index bf277057b1a6..c66c3c04e0e9 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs @@ -142,7 +142,7 @@ public partial interface IMultiplexedConnectionFactory public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable { System.Net.EndPoint EndPoint { get; } - System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } public partial interface IMultiplexedConnectionListenerFactory @@ -156,11 +156,6 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } } - public static partial class MultiplexedConnectionBuilderExtensions - { - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Run(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } - } public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } @@ -171,7 +166,7 @@ protected MultiplexedConnectionContext() { } public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext { protected StreamContext() { } - public abstract long StreamId { get; } + public abstract string StreamId { get; } } [System.FlagsAttribute] public enum TransferFormat @@ -244,6 +239,10 @@ public partial interface IStreamDirectionFeature bool CanRead { get; } bool CanWrite { get; } } + public partial interface IStreamIdFeature + { + long StreamId { get; } + } public partial interface ITlsHandshakeFeature { System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; } diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index bf277057b1a6..c66c3c04e0e9 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -142,7 +142,7 @@ public partial interface IMultiplexedConnectionFactory public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable { System.Net.EndPoint EndPoint { get; } - System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } public partial interface IMultiplexedConnectionListenerFactory @@ -156,11 +156,6 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } } - public static partial class MultiplexedConnectionBuilderExtensions - { - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Run(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } - } public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } @@ -171,7 +166,7 @@ protected MultiplexedConnectionContext() { } public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext { protected StreamContext() { } - public abstract long StreamId { get; } + public abstract string StreamId { get; } } [System.FlagsAttribute] public enum TransferFormat @@ -244,6 +239,10 @@ public partial interface IStreamDirectionFeature bool CanRead { get; } bool CanWrite { get; } } + public partial interface IStreamIdFeature + { + long StreamId { get; } + } public partial interface ITlsHandshakeFeature { System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; } diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs index bf277057b1a6..c66c3c04e0e9 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs @@ -142,7 +142,7 @@ public partial interface IMultiplexedConnectionFactory public partial interface IMultiplexedConnectionListener : System.IAsyncDisposable { System.Net.EndPoint EndPoint { get; } - System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.ValueTask AcceptAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); System.Threading.Tasks.ValueTask UnbindAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } public partial interface IMultiplexedConnectionListenerFactory @@ -156,11 +156,6 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) public Microsoft.AspNetCore.Connections.MultiplexedConnectionDelegate Build() { throw null; } public Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Use(System.Func middleware) { throw null; } } - public static partial class MultiplexedConnectionBuilderExtensions - { - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder Run(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func middleware) { throw null; } - public static Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder UseMultiplexed(this Microsoft.AspNetCore.Connections.IMultiplexedConnectionBuilder connectionBuilder, System.Func, System.Threading.Tasks.Task> middleware) { throw null; } - } public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } @@ -171,7 +166,7 @@ protected MultiplexedConnectionContext() { } public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext { protected StreamContext() { } - public abstract long StreamId { get; } + public abstract string StreamId { get; } } [System.FlagsAttribute] public enum TransferFormat @@ -244,6 +239,10 @@ public partial interface IStreamDirectionFeature bool CanRead { get; } bool CanWrite { get; } } + public partial interface IStreamIdFeature + { + long StreamId { get; } + } public partial interface ITlsHandshakeFeature { System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; } diff --git a/src/Servers/Connections.Abstractions/src/Features/IStreamIdFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IStreamIdFeature.cs new file mode 100644 index 000000000000..f86f2ee61e6e --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/Features/IStreamIdFeature.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Connections.Features +{ + public interface IStreamIdFeature + { + long StreamId { get; } + } +} diff --git a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs index b4d9bcf80c48..d867fe0938de 100644 --- a/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs +++ b/src/Servers/Connections.Abstractions/src/IMulitplexedConnectionListener.cs @@ -5,6 +5,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Connections { @@ -28,8 +29,9 @@ public interface IMultiplexedConnectionListener : IAsyncDisposable /// /// Begins an asynchronous operation to accept an incoming connection. /// + /// A feature collection to pass options when accepting a connection. /// The token to monitor for cancellation requests. /// A that completes when a connection is accepted, yielding the representing the connection. - ValueTask AcceptAsync(CancellationToken cancellationToken = default); + ValueTask AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Connections.Abstractions/src/StreamContext.cs b/src/Servers/Connections.Abstractions/src/StreamContext.cs index 4dd455e98327..56cac86b4c79 100644 --- a/src/Servers/Connections.Abstractions/src/StreamContext.cs +++ b/src/Servers/Connections.Abstractions/src/StreamContext.cs @@ -8,6 +8,6 @@ public abstract class StreamContext : ConnectionContext /// /// Gets the id assigned to the stream. /// - public abstract long StreamId { get; } + public abstract string StreamId { get; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 91c2d634ddd0..d90f01bfe4fd 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -205,13 +205,14 @@ internal async Task InnerProcessRequestsAsync(IHttpApplication(); + var quicStreamFeature = streamContext.Features.Get(); + var streamIdFeature = streamContext.Features.Get(); - Debug.Assert(streamDirectionFeature != null); + Debug.Assert(quicStreamFeature != null); var httpConnectionContext = new Http3StreamContext { - ConnectionId = streamContext.StreamId.ToString(), + ConnectionId = streamContext.StreamId, StreamContext = streamContext, // TODO connection context is null here. Should we set it to anything? ServiceContext = _context.ServiceContext, @@ -223,7 +224,7 @@ internal async Task InnerProcessRequestsAsync(IHttpApplication(application, this, httpConnectionContext); @@ -232,7 +233,7 @@ internal async Task InnerProcessRequestsAsync(IHttpApplication(application, this, httpConnectionContext); diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs index 71866dc2959e..2f0a8e153fed 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { @@ -36,7 +37,7 @@ public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndP public EndPoint EndPoint { get; set; } - public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + public async ValueTask AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) { var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); try diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs index 1aafa00941e3..ef3911e75c9f 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal class QuicStreamContext : TransportStream, IStreamDirectionFeature, IProtocolErrorCodeFeature + internal class QuicStreamContext : TransportStream, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature { private readonly Task _processingTask; private readonly QuicStream _stream; @@ -67,7 +67,7 @@ public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, Qu public bool CanRead { get; } public bool CanWrite { get; } - public override long StreamId + long IStreamIdFeature.StreamId { get { @@ -75,6 +75,14 @@ public override long StreamId } } + public override string StreamId + { + get + { + return _stream.StreamId.ToString(); + } + } + public override string ConnectionId { get diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index 328d0d695c73..113517302e5b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -479,18 +479,20 @@ public override ValueTask ConnectAsync(IFeatureCollection feature } } - private class TestStreamContext : StreamContext + private class TestStreamContext : StreamContext, IStreamDirectionFeature, IStreamIdFeature { private DuplexPipePair _pair; public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair, IProtocolErrorCodeFeature feature) { _pair = pair; Features = new FeatureCollection(); - Features.Set(new DefaultStreamDirectionFeature(canRead, canWrite)); + Features.Set(this); + CanRead = canRead; + CanWrite = canWrite; Features.Set(feature); } - public override long StreamId { get; } + public override string StreamId { get; } public override string ConnectionId { get; set; } @@ -510,6 +512,12 @@ public override IDuplexPipe Transport } } + public bool CanRead { get; } + + public bool CanWrite { get; } + + long IStreamIdFeature.StreamId { get; } + public override void Abort(ConnectionAbortedException abortReason) { _pair.Application.Output.Complete(abortReason); From dae4b3d6e03bf5cfe8d6ab0b63fe28c2b2cad254 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 24 Feb 2020 13:47:53 -0800 Subject: [PATCH 25/27] Feedback --- .../src/BaseConnectionContext.cs | 3 +- .../src/MultiplexedConnectionContext.cs | 7 +- .../src/StreamContext.cs | 13 - .../src/Internal/Http3/Http3Connection.cs | 33 ++- .../Core/src/Internal/Http3/Http3Stream.cs | 10 +- .../Core/src/Internal/Http3StreamContext.cs | 7 +- .../MultiplexedConnectionDispatcher.cs | 1 - src/Servers/Kestrel/Core/src/KestrelServer.cs | 16 +- .../Kestrel/test/GeneratedCodeTests.cs | 11 +- ...oft.AspNetCore.Server.Kestrel.Tests.csproj | 1 + .../src/Internal/QuicConnectionContext.cs | 26 +- .../src/Internal/QuicConnectionListener.cs | 12 +- .../src/Internal/QuicStreamContext.cs | 13 +- ...tCore.Server.Kestrel.Transport.Quic.csproj | 8 +- ...ransportMultiplexedConnection.Generated.cs | 36 ++- .../TransportStream.FeatureCollection.cs | 44 ---- .../shared/TransportStream.Generated.cs | 248 ------------------ src/Servers/Kestrel/shared/TransportStream.cs | 78 ------ .../Http3/Http3TestBase.cs | 28 +- .../InMemory.FunctionalTests.csproj | 4 +- .../tools/CodeGenerator/CodeGenerator.csproj | 2 +- .../Kestrel/tools/CodeGenerator/Program.cs | 18 +- .../TransportConnectionFeatureCollection.cs | 7 +- .../TransportStreamFeatureCollection.cs | 37 --- .../MsQuic/Internal/MsQuicApi.cs | 8 +- 25 files changed, 116 insertions(+), 555 deletions(-) delete mode 100644 src/Servers/Connections.Abstractions/src/StreamContext.cs delete mode 100644 src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs delete mode 100644 src/Servers/Kestrel/shared/TransportStream.Generated.cs delete mode 100644 src/Servers/Kestrel/shared/TransportStream.cs delete mode 100644 src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs diff --git a/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs b/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs index 1c0e61436957..662b8c902e9b 100644 --- a/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/BaseConnectionContext.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Net; using System.Threading; @@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Connections { - public abstract class BaseConnectionContext + public abstract class BaseConnectionContext : IAsyncDisposable { /// /// Gets or sets a unique identifier to represent this connection in trace logs. diff --git a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs index 350eabb6835a..ce0850d2816f 100644 --- a/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/MultiplexedConnectionContext.cs @@ -8,6 +8,9 @@ namespace Microsoft.AspNetCore.Connections { + /// + /// Encapsulates all information about a multiplexed connection. + /// public abstract class MultiplexedConnectionContext : BaseConnectionContext, IAsyncDisposable { /// @@ -15,7 +18,7 @@ public abstract class MultiplexedConnectionContext : BaseConnectionContext, IAsy /// /// /// - public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); + public abstract ValueTask AcceptAsync(CancellationToken cancellationToken = default); /// /// Creates an outbound connection @@ -23,6 +26,6 @@ public abstract class MultiplexedConnectionContext : BaseConnectionContext, IAsy /// /// /// - public abstract ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); + public abstract ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default); } } diff --git a/src/Servers/Connections.Abstractions/src/StreamContext.cs b/src/Servers/Connections.Abstractions/src/StreamContext.cs deleted file mode 100644 index 56cac86b4c79..000000000000 --- a/src/Servers/Connections.Abstractions/src/StreamContext.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Connections -{ - public abstract class StreamContext : ConnectionContext - { - /// - /// Gets the id assigned to the stream. - /// - public abstract string StreamId { get; } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index d90f01bfe4fd..3aa7d990a310 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Http; @@ -26,7 +27,7 @@ internal class Http3Connection : IRequestProcessor, ITimeoutHandler public Http3ControlStream EncoderStream { get; set; } public Http3ControlStream DecoderStream { get; set; } - private readonly ConcurrentDictionary _streams = new ConcurrentDictionary(); + internal readonly Dictionary _streams = new Dictionary(); private long _highestOpenedStreamId; // TODO lock to access private volatile bool _haveSentGoAway; @@ -212,7 +213,7 @@ internal async Task InnerProcessRequestsAsync(IHttpApplication(IHttpApplication(application, this, httpConnectionContext); var stream = http3Stream; - _streams[streamId] = http3Stream; + lock (_streams) + { + _streams[streamId] = http3Stream; + } ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } @@ -246,9 +250,12 @@ internal async Task InnerProcessRequestsAsync(IHttpApplication(); + _streamIdFeature = _context.ConnectionFeatures.Get(); _frameWriter = new Http3FrameWriter( context.Transport.Output, @@ -297,7 +299,6 @@ public void HandleRequestHeadersTimeout() public void OnInputOrOutputCompleted() { - Log.LogTrace("On input or output completed"); TryClose(); Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), Http3ErrorCode.NoError); } @@ -353,7 +354,6 @@ public async Task ProcessRequestAsync(IHttpApplication appli catch (Http3StreamErrorException ex) { error = ex; - //errorCode = ex.ErrorCode; Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode); } catch (Exception ex) @@ -366,10 +366,8 @@ public async Task ProcessRequestAsync(IHttpApplication appli var streamError = error as ConnectionAbortedException ?? new ConnectionAbortedException("The stream has completed.", error); - // Input has completed. - Input.Complete(); - _context.Transport.Input.CancelPendingRead(); + await RequestBodyPipe.Writer.CompleteAsync(); // Make sure application func is completed before completing writer. @@ -386,6 +384,7 @@ public async Task ProcessRequestAsync(IHttpApplication appli } finally { + _http3Connection.RemoveStream(_streamIdFeature.StreamId); } } } @@ -727,7 +726,6 @@ private enum RequestHeaderParsingState Trailers } - [Flags] private enum PseudoHeaderFields { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs b/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs index 0e99eb88de19..9107b6dc8406 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3StreamContext.cs @@ -1,17 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Buffers; -using System.IO.Pipelines; -using System.Net; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { internal class Http3StreamContext : HttpConnectionContext { - public StreamContext StreamContext { get; set; } + public ConnectionContext StreamContext { get; set; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs index 61b03c73415a..e0fe1edbdc72 100644 --- a/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/MultiplexedConnectionDispatcher.cs @@ -53,7 +53,6 @@ async Task AcceptConnectionsAsync() // Add the connection to the connection manager before we queue it for execution var id = Interlocked.Increment(ref _lastConnectionId); - // TODO Don't pass null in here! use a base class var kestrelConnection = new KestrelConnection(id, _serviceContext, c => _connectionDelegate(c), connection, Log); _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection); diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 05030da086be..fdd2b47319cf 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -148,7 +148,7 @@ async Task OnBind(ListenOptions options) // sockets for it to successfully listen. It also seems racy. if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { - if (_multiplexedTransportFactories == null) + if (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0) { throw new InvalidOperationException("Cannot start HTTP/3 server if no MultiplexedTransportFactories are registered."); } @@ -213,19 +213,19 @@ public async Task StopAsync(CancellationToken cancellationToken) try { - var transportCount = _transports.Count; + var connectionTransportCount = _transports.Count; var totalTransportCount = _transports.Count + _multiplexedTransports.Count; var tasks = new Task[totalTransportCount]; - for (int i = 0; i < transportCount; i++) + for (int i = 0; i < connectionTransportCount; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); } - for (int i = transportCount; i < totalTransportCount; i++) + for (int i = connectionTransportCount; i < totalTransportCount; i++) { - (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - transportCount]; + (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - connectionTransportCount]; tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); } @@ -241,15 +241,15 @@ public async Task StopAsync(CancellationToken cancellationToken) } } - for (int i = 0; i < _transports.Count; i++) + for (int i = 0; i < connectionTransportCount; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; tasks[i] = listener.DisposeAsync().AsTask(); } - for (int i = _transports.Count; i < totalTransportCount; i++) + for (int i = connectionTransportCount; i < totalTransportCount; i++) { - (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - transportCount]; + (IMultiplexedConnectionListener listener, Task acceptLoop) = _multiplexedTransports[i - connectionTransportCount]; tasks[i] = listener.DisposeAsync().AsTask(); } diff --git a/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs b/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs index 4b1154229f58..f2b8985906bf 100644 --- a/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/GeneratedCodeTests.cs @@ -20,9 +20,8 @@ public void GeneratedCodeIsUpToDate() var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.Generated.cs"); var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpUtilities.Generated.cs"); var http2ConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "Http2Connection.Generated.cs"); - var transportMultiplexedConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportConnectionBase.Generated.cs"); + var transportMultiplexedConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportMultiplexedConnection.Generated.cs"); var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportConnection.Generated.cs"); - var transportStreamGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportStream.Generated.cs"); var testHttpHeadersGeneratedPath = Path.GetTempFileName(); var testHttpProtocolGeneratedPath = Path.GetTempFileName(); @@ -30,7 +29,6 @@ public void GeneratedCodeIsUpToDate() var testHttp2ConnectionGeneratedPath = Path.GetTempFileName(); var testTransportMultiplexedConnectionGeneratedPath = Path.GetTempFileName(); var testTransportConnectionGeneratedPath = Path.GetTempFileName(); - var testTransportStreamGeneratedPath = Path.GetTempFileName(); try { @@ -40,15 +38,13 @@ public void GeneratedCodeIsUpToDate() var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath); var currentTransportConnectionBaseGenerated = File.ReadAllText(transportMultiplexedConnectionGeneratedPath); var currentTransportConnectionGenerated = File.ReadAllText(transportConnectionGeneratedPath); - var currentTransportStreamGenerated = File.ReadAllText(transportStreamGeneratedPath); CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, testHttpProtocolGeneratedPath, testHttpUtilitiesGeneratedPath, testHttp2ConnectionGeneratedPath, testTransportMultiplexedConnectionGeneratedPath, - testTransportConnectionGeneratedPath, - testTransportStreamGeneratedPath); + testTransportConnectionGeneratedPath); var testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath); var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath); @@ -56,7 +52,6 @@ public void GeneratedCodeIsUpToDate() var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath); var testTransportMultiplxedConnectionGenerated = File.ReadAllText(testTransportMultiplexedConnectionGeneratedPath); var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath); - var testTransportStreamGenerated = File.ReadAllText(testTransportStreamGeneratedPath); Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true); @@ -64,7 +59,6 @@ public void GeneratedCodeIsUpToDate() Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentTransportConnectionBaseGenerated, testTransportMultiplxedConnectionGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true); - Assert.Equal(currentTransportStreamGenerated, testTransportStreamGenerated, ignoreLineEndingDifferences: true); } finally { @@ -74,7 +68,6 @@ public void GeneratedCodeIsUpToDate() File.Delete(testHttp2ConnectionGeneratedPath); File.Delete(testTransportMultiplexedConnectionGeneratedPath); File.Delete(testTransportConnectionGeneratedPath); - File.Delete(testTransportStreamGeneratedPath); } } } diff --git a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj index 33eda7eb1b93..abf2fb6b3a1a 100644 --- a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj +++ b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index ef3cea228642..513d45731c9b 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -33,16 +33,16 @@ public QuicConnectionContext(QuicConnection connection, QuicTransportContext con _log.NewConnection(ConnectionId); } - public ValueTask StartUnidirectionalStreamAsync() + public ValueTask StartUnidirectionalStreamAsync() { var stream = _connection.OpenUnidirectionalStream(); - return new ValueTask(new QuicStreamContext(stream, this, _context)); + return new ValueTask(new QuicStreamContext(stream, this, _context)); } - public ValueTask StartBidirectionalStreamAsync() + public ValueTask StartBidirectionalStreamAsync() { var stream = _connection.OpenBidirectionalStream(); - return new ValueTask(new QuicStreamContext(stream, this, _context)); + return new ValueTask(new QuicStreamContext(stream, this, _context)); } public override async ValueTask DisposeAsync() @@ -74,21 +74,23 @@ public override void Abort(ConnectionAbortedException abortReason) _closeTask = _connection.CloseAsync(errorCode: Error); } - public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { - var stream = await _connection.AcceptStreamAsync(cancellationToken); try { - _ = stream.CanRead; + var stream = await _connection.AcceptStreamAsync(cancellationToken); + return new QuicStreamContext(stream, this, _context); } - catch (Exception) + catch (QuicException ex) { - return null; + // Accept on graceful close throws an aborted exception rather than returning null. + _log.LogDebug($"Accept loop ended with exception: {ex.Message}"); } - return new QuicStreamContext(stream, this, _context); + + return null; } - public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) + public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) { QuicStream quicStream; @@ -109,7 +111,7 @@ public override ValueTask ConnectAsync(IFeatureCollection feature quicStream = _connection.OpenBidirectionalStream(); } - return new ValueTask(new QuicStreamContext(quicStream, this, _context)); + return new ValueTask(new QuicStreamContext(quicStream, this, _context)); } } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs index 2f0a8e153fed..1b36d90ab0a7 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { @@ -39,17 +40,16 @@ public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndP public async ValueTask AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) { - var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); try { - _ = quicConnection.LocalEndPoint; + var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); + return new QuicConnectionContext(quicConnection, _context); } - catch (Exception) + catch (QuicOperationAbortedException ex) { - return null; + _log.LogDebug($"Listener has aborted with exception: {ex.Message}"); } - - return new QuicConnectionContext(quicConnection, _context); + return null; } public async ValueTask UnbindAsync(CancellationToken cancellationToken = default) diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs index ef3911e75c9f..5b841aa097e9 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal class QuicStreamContext : TransportStream, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature + internal class QuicStreamContext : TransportConnection, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature { private readonly Task _processingTask; private readonly QuicStream _stream; @@ -48,6 +48,7 @@ public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, Qu Features.Set(this); Features.Set(this); + Features.Set(this); // TODO populate the ITlsConnectionFeature (requires client certs). Features.Set(new FakeTlsConnectionFeature()); @@ -67,7 +68,7 @@ public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, Qu public bool CanRead { get; } public bool CanWrite { get; } - long IStreamIdFeature.StreamId + public long StreamId { get { @@ -75,14 +76,6 @@ long IStreamIdFeature.StreamId } } - public override string StreamId - { - get - { - return _stream.StreamId.ToString(); - } - } - public override string ConnectionId { get diff --git a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj index 7d62efe33867..22e92069b1db 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj +++ b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj @@ -1,4 +1,4 @@ - + Quic transport for the ASP.NET Core Kestrel cross-platform web server. @@ -17,9 +17,9 @@ - - - + + + diff --git a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs index 41fec23fc846..e7df1de198e7 100644 --- a/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs +++ b/src/Servers/Kestrel/shared/TransportMultiplexedConnection.Generated.cs @@ -12,12 +12,6 @@ namespace Microsoft.AspNetCore.Connections { internal partial class TransportMultiplexedConnection : IFeatureCollection { - private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); - private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); - private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); - private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); - private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); - private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; private object _currentIConnectionItemsFeature; @@ -90,23 +84,23 @@ object IFeatureCollection.this[Type key] get { object feature = null; - if (key == IConnectionIdFeatureType) + if (key == typeof(IConnectionIdFeature)) { feature = _currentIConnectionIdFeature; } - else if (key == IConnectionTransportFeatureType) + else if (key == typeof(IConnectionTransportFeature)) { feature = _currentIConnectionTransportFeature; } - else if (key == IConnectionItemsFeatureType) + else if (key == typeof(IConnectionItemsFeature)) { feature = _currentIConnectionItemsFeature; } - else if (key == IMemoryPoolFeatureType) + else if (key == typeof(IMemoryPoolFeature)) { feature = _currentIMemoryPoolFeature; } - else if (key == IConnectionLifetimeFeatureType) + else if (key == typeof(IConnectionLifetimeFeature)) { feature = _currentIConnectionLifetimeFeature; } @@ -122,23 +116,23 @@ object IFeatureCollection.this[Type key] { _featureRevision++; - if (key == IConnectionIdFeatureType) + if (key == typeof(IConnectionIdFeature)) { _currentIConnectionIdFeature = value; } - else if (key == IConnectionTransportFeatureType) + else if (key == typeof(IConnectionTransportFeature)) { _currentIConnectionTransportFeature = value; } - else if (key == IConnectionItemsFeatureType) + else if (key == typeof(IConnectionItemsFeature)) { _currentIConnectionItemsFeature = value; } - else if (key == IMemoryPoolFeatureType) + else if (key == typeof(IMemoryPoolFeature)) { _currentIMemoryPoolFeature = value; } - else if (key == IConnectionLifetimeFeatureType) + else if (key == typeof(IConnectionLifetimeFeature)) { _currentIConnectionLifetimeFeature = value; } @@ -213,23 +207,23 @@ private IEnumerable> FastEnumerable() { if (_currentIConnectionIdFeature != null) { - yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); + yield return new KeyValuePair(typeof(IConnectionIdFeature), _currentIConnectionIdFeature); } if (_currentIConnectionTransportFeature != null) { - yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); + yield return new KeyValuePair(typeof(IConnectionTransportFeature), _currentIConnectionTransportFeature); } if (_currentIConnectionItemsFeature != null) { - yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); + yield return new KeyValuePair(typeof(IConnectionItemsFeature), _currentIConnectionItemsFeature); } if (_currentIMemoryPoolFeature != null) { - yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); + yield return new KeyValuePair(typeof(IMemoryPoolFeature), _currentIMemoryPoolFeature); } if (_currentIConnectionLifetimeFeature != null) { - yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); + yield return new KeyValuePair(typeof(IConnectionLifetimeFeature), _currentIConnectionLifetimeFeature); } if (MaybeExtra != null) diff --git a/src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs b/src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs deleted file mode 100644 index f97e87be73e3..000000000000 --- a/src/Servers/Kestrel/shared/TransportStream.FeatureCollection.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Collections.Generic; -using System.IO.Pipelines; -using System.Threading; -using Microsoft.AspNetCore.Connections.Features; - -namespace Microsoft.AspNetCore.Connections -{ - internal partial class TransportStream : IConnectionIdFeature, - IConnectionTransportFeature, - IConnectionItemsFeature, - IMemoryPoolFeature, - IConnectionLifetimeFeature - { - // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, - // then the list of `features` in the generated code project MUST also be updated. - // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs - - MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool; - - IDuplexPipe IConnectionTransportFeature.Transport - { - get => Transport; - set => Transport = value; - } - - IDictionary IConnectionItemsFeature.Items - { - get => Items; - set => Items = value; - } - - CancellationToken IConnectionLifetimeFeature.ConnectionClosed - { - get => ConnectionClosed; - set => ConnectionClosed = value; - } - - void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); - } -} diff --git a/src/Servers/Kestrel/shared/TransportStream.Generated.cs b/src/Servers/Kestrel/shared/TransportStream.Generated.cs deleted file mode 100644 index 1a81910a7f78..000000000000 --- a/src/Servers/Kestrel/shared/TransportStream.Generated.cs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections; -using System.Collections.Generic; - -using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http.Features; - -namespace Microsoft.AspNetCore.Connections -{ - internal partial class TransportStream : IFeatureCollection - { - private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); - private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); - private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); - private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); - private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); - - private object _currentIConnectionIdFeature; - private object _currentIConnectionTransportFeature; - private object _currentIConnectionItemsFeature; - private object _currentIMemoryPoolFeature; - private object _currentIConnectionLifetimeFeature; - - private int _featureRevision; - - private List> MaybeExtra; - - private void FastReset() - { - _currentIConnectionIdFeature = this; - _currentIConnectionTransportFeature = this; - _currentIConnectionItemsFeature = this; - _currentIMemoryPoolFeature = this; - _currentIConnectionLifetimeFeature = this; - - } - - // Internal for testing - internal void ResetFeatureCollection() - { - FastReset(); - MaybeExtra?.Clear(); - _featureRevision++; - } - - private object ExtraFeatureGet(Type key) - { - if (MaybeExtra == null) - { - return null; - } - for (var i = 0; i < MaybeExtra.Count; i++) - { - var kv = MaybeExtra[i]; - if (kv.Key == key) - { - return kv.Value; - } - } - return null; - } - - private void ExtraFeatureSet(Type key, object value) - { - if (MaybeExtra == null) - { - MaybeExtra = new List>(2); - } - - for (var i = 0; i < MaybeExtra.Count; i++) - { - if (MaybeExtra[i].Key == key) - { - MaybeExtra[i] = new KeyValuePair(key, value); - return; - } - } - MaybeExtra.Add(new KeyValuePair(key, value)); - } - - bool IFeatureCollection.IsReadOnly => false; - - int IFeatureCollection.Revision => _featureRevision; - - object IFeatureCollection.this[Type key] - { - get - { - object feature = null; - if (key == IConnectionIdFeatureType) - { - feature = _currentIConnectionIdFeature; - } - else if (key == IConnectionTransportFeatureType) - { - feature = _currentIConnectionTransportFeature; - } - else if (key == IConnectionItemsFeatureType) - { - feature = _currentIConnectionItemsFeature; - } - else if (key == IMemoryPoolFeatureType) - { - feature = _currentIMemoryPoolFeature; - } - else if (key == IConnectionLifetimeFeatureType) - { - feature = _currentIConnectionLifetimeFeature; - } - else if (MaybeExtra != null) - { - feature = ExtraFeatureGet(key); - } - - return feature; - } - - set - { - _featureRevision++; - - if (key == IConnectionIdFeatureType) - { - _currentIConnectionIdFeature = value; - } - else if (key == IConnectionTransportFeatureType) - { - _currentIConnectionTransportFeature = value; - } - else if (key == IConnectionItemsFeatureType) - { - _currentIConnectionItemsFeature = value; - } - else if (key == IMemoryPoolFeatureType) - { - _currentIMemoryPoolFeature = value; - } - else if (key == IConnectionLifetimeFeatureType) - { - _currentIConnectionLifetimeFeature = value; - } - else - { - ExtraFeatureSet(key, value); - } - } - } - - TFeature IFeatureCollection.Get() - { - TFeature feature = default; - if (typeof(TFeature) == typeof(IConnectionIdFeature)) - { - feature = (TFeature)_currentIConnectionIdFeature; - } - else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) - { - feature = (TFeature)_currentIConnectionTransportFeature; - } - else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) - { - feature = (TFeature)_currentIConnectionItemsFeature; - } - else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) - { - feature = (TFeature)_currentIMemoryPoolFeature; - } - else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) - { - feature = (TFeature)_currentIConnectionLifetimeFeature; - } - else if (MaybeExtra != null) - { - feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); - } - - return feature; - } - - void IFeatureCollection.Set(TFeature feature) - { - _featureRevision++; - if (typeof(TFeature) == typeof(IConnectionIdFeature)) - { - _currentIConnectionIdFeature = feature; - } - else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) - { - _currentIConnectionTransportFeature = feature; - } - else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) - { - _currentIConnectionItemsFeature = feature; - } - else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) - { - _currentIMemoryPoolFeature = feature; - } - else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) - { - _currentIConnectionLifetimeFeature = feature; - } - else - { - ExtraFeatureSet(typeof(TFeature), feature); - } - } - - private IEnumerable> FastEnumerable() - { - if (_currentIConnectionIdFeature != null) - { - yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); - } - if (_currentIConnectionTransportFeature != null) - { - yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); - } - if (_currentIConnectionItemsFeature != null) - { - yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); - } - if (_currentIMemoryPoolFeature != null) - { - yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); - } - if (_currentIConnectionLifetimeFeature != null) - { - yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); - } - - if (MaybeExtra != null) - { - foreach (var item in MaybeExtra) - { - yield return item; - } - } - } - - IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); - } -} diff --git a/src/Servers/Kestrel/shared/TransportStream.cs b/src/Servers/Kestrel/shared/TransportStream.cs deleted file mode 100644 index be77c0a96d8a..000000000000 --- a/src/Servers/Kestrel/shared/TransportStream.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO.Pipelines; -using System.Net; -using System.Threading; -using Microsoft.AspNetCore.Http.Features; - -namespace Microsoft.AspNetCore.Connections -{ - internal abstract partial class TransportStream : StreamContext - { - private IDictionary _items; - private string _connectionId; - - public TransportStream() - { - FastReset(); - } - - public override EndPoint LocalEndPoint { get; set; } - public override EndPoint RemoteEndPoint { get; set; } - - public override string ConnectionId - { - get - { - if (_connectionId == null) - { - _connectionId = CorrelationIdGenerator.GetNextId(); - } - - return _connectionId; - } - set - { - _connectionId = value; - } - } - - public override IFeatureCollection Features => this; - - public virtual MemoryPool MemoryPool { get; } - - public override IDuplexPipe Transport { get; set; } - - public IDuplexPipe Application { get; set; } - - public override IDictionary Items - { - get - { - // Lazily allocate connection metadata - return _items ?? (_items = new ConnectionItems()); - } - set - { - _items = value; - } - } - - public override CancellationToken ConnectionClosed { get; set; } - - // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause - // any TransportConnection that does not override Abort or calls base.Abort - // to stack overflow when IConnectionLifetimeFeature.Abort() is called. - // That said, all derived types should override this method should override - // this implementation of Abort because canceling pending output reads is not - // sufficient to abort the connection if there is backpressure. - public override void Abort(ConnectionAbortedException abortReason) - { - Application.Input.CancelPendingRead(); - } - } -} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index 113517302e5b..f767fc08718c 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -195,11 +195,11 @@ internal ValueTask CreateRequestStream() return new ValueTask(stream); } - public ValueTask StartBidirectionalStreamAsync() + public ValueTask StartBidirectionalStreamAsync() { var stream = new Http3RequestStream(this, _connection); // TODO put these somewhere to be read. - return new ValueTask(stream.StreamContext); + return new ValueTask(stream.StreamContext); } internal class Http3StreamBase @@ -223,7 +223,7 @@ protected static async Task FlushAsync(PipeWriter writableBuffer) internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler, IProtocolErrorCodeFeature { - internal StreamContext StreamContext { get; } + internal ConnectionContext StreamContext { get; } public bool CanRead => true; public bool CanWrite => true; @@ -394,7 +394,7 @@ public Http3FrameWithPayload() : base() internal class Http3ControlStream : Http3StreamBase, IProtocolErrorCodeFeature { - internal StreamContext StreamContext { get; } + internal ConnectionContext StreamContext { get; } public bool CanRead => true; public bool CanWrite => false; @@ -431,7 +431,7 @@ void WriteSpan(PipeWriter pw) private class TestMultiplexedConnectionContext : MultiplexedConnectionContext { - public readonly Channel AcceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions + public readonly Channel AcceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true, SingleWriter = true @@ -458,7 +458,7 @@ public override void Abort(ConnectionAbortedException abortReason) { } - public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { while (await AcceptQueue.Reader.WaitToReadAsync()) { @@ -471,15 +471,15 @@ public override async ValueTask AcceptAsync(CancellationToken can return null; } - public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) + public override ValueTask ConnectAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default) { var stream = new Http3ControlStream(_testBase); // TODO put these somewhere to be read. - return new ValueTask(stream.StreamContext); + return new ValueTask(stream.StreamContext); } } - private class TestStreamContext : StreamContext, IStreamDirectionFeature, IStreamIdFeature + private class TestStreamContext : ConnectionContext, IStreamDirectionFeature, IStreamIdFeature { private DuplexPipePair _pair; public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair, IProtocolErrorCodeFeature feature) @@ -487,15 +487,17 @@ public TestStreamContext(bool canRead, bool canWrite, DuplexPipePair pair, IProt _pair = pair; Features = new FeatureCollection(); Features.Set(this); + Features.Set(this); + Features.Set(feature); + CanRead = canRead; CanWrite = canWrite; - Features.Set(feature); } - public override string StreamId { get; } - public override string ConnectionId { get; set; } + public long StreamId { get; } + public override IFeatureCollection Features { get; } public override IDictionary Items { get; set; } @@ -516,8 +518,6 @@ public override IDuplexPipe Transport public bool CanWrite { get; } - long IStreamIdFeature.StreamId { get; } - public override void Abort(ConnectionAbortedException abortReason) { _pair.Application.Output.Complete(abortReason); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index b4442f73ba1a..cad32b08bd5f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -27,4 +27,4 @@ - + \ No newline at end of file diff --git a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj index ac8debc0a3ae..b79bf13aa714 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj +++ b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj @@ -18,7 +18,7 @@ $(MSBuildThisFileDirectory)..\..\ - Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs shared/TransportMultiplexedConnection.Generated.cs shared/TransportConnection.Generated.cs shared/TransportStream.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs + Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs shared/TransportMultiplexedConnection.Generated.cs shared/TransportConnection.Generated.cs diff --git a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs index 7047249a8470..48b1fa605f0e 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs @@ -27,26 +27,21 @@ public static int Main(string[] args) } else if (args.Length < 4) { - Console.Error.WriteLine("Missing path to TransportMultiplexedConnection.Generated.cs"); + Console.Error.WriteLine("Missing path to Http2Connection.Generated.cs"); return 1; } else if (args.Length < 5) { - Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs"); + Console.Error.WriteLine("Missing path to TransportMultiplexedConnection.Generated.cs"); return 1; } else if (args.Length < 6) { - Console.Error.WriteLine("Missing path to Http2Connection.Generated.cs"); - return 1; - } - else if (args.Length < 7) - { - Console.Error.WriteLine("Missing path to TransportStream.Generated.cs"); + Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs"); return 1; } - Run(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + Run(args[0], args[1], args[2], args[3], args[4], args[5]); return 0; } @@ -57,8 +52,7 @@ public static void Run( string httpUtilitiesPath, string http2ConnectionPath, string transportMultiplexedConnectionFeatureCollectionPath, - string transportConnectionFeatureCollectionPath, - string transportStreamFeatureCollectionPath) + string transportConnectionFeatureCollectionPath) { var knownHeadersContent = KnownHeaders.GeneratedFile(); var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile(); @@ -66,7 +60,6 @@ public static void Run( var transportMultiplexedConnectionFeatureCollectionContent = TransportMultiplexedConnectionFeatureCollection.GenerateFile(); var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile(); var http2ConnectionContent = Http2Connection.GenerateFile(); - var transportStreamFeatureCollectionContent = TransportStreamFeatureCollection.GenerateFile(); UpdateFile(knownHeadersPath, knownHeadersContent); UpdateFile(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); @@ -74,7 +67,6 @@ public static void Run( UpdateFile(http2ConnectionPath, http2ConnectionContent); UpdateFile(transportMultiplexedConnectionFeatureCollectionPath, transportMultiplexedConnectionFeatureCollectionContent); UpdateFile(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); - UpdateFile(transportStreamFeatureCollectionPath, transportStreamFeatureCollectionContent); } public static void UpdateFile(string path, string content) diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index 4da91d15374f..fdc759e7f31f 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace CodeGenerator { diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs deleted file mode 100644 index a6829209f017..000000000000 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportStreamFeatureCollection.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodeGenerator -{ - public class TransportStreamFeatureCollection - { - public static string GenerateFile() - { - // NOTE: This list MUST always match the set of feature interfaces implemented by TransportStream. - // See also: shared/TransportStream.FeatureCollection.cs - var features = new[] - { - "IConnectionIdFeature", - "IConnectionTransportFeature", - "IConnectionItemsFeature", - "IMemoryPoolFeature", - "IConnectionLifetimeFeature" - }; - - var usings = $@" -using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http.Features;"; - - return FeatureCollectionGenerator.GenerateFile( - namespaceName: "Microsoft.AspNetCore.Connections", - className: "TransportStream", - allFeatures: features, - implementedFeatures: features, - extraUsings: usings, - fallbackFeatures: null); - } - } -} diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index 30e5cba6cbed..fb6330c024b1 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -143,12 +143,6 @@ static MsQuicApi() OperatingSystem ver = Environment.OSVersion; - if (ver.Platform == PlatformID.Win32NT && ver.Version < new Version(10, 0, 19041, 0)) - { - IsQuicSupported = false; - return; - } - // TODO: try to initialize TLS 1.3 in SslStream. try From f5d328608e399f39c7e769d9eeac91455b6a5dcf Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 24 Feb 2020 15:03:06 -0800 Subject: [PATCH 26/27] Feedback --- .../Web.JS/dist/Release/blazor.server.js | 2 +- ...ore.Connections.Abstractions.netcoreapp.cs | 11 +++------- ...Connections.Abstractions.netstandard2.0.cs | 11 +++------- ...Connections.Abstractions.netstandard2.1.cs | 11 +++------- .../Core/src/Internal/Http3/Http3Stream.cs | 20 ++++++++++++++----- .../Core/src/Internal/Http3/Http3StreamOfT.cs | 9 ++++++++- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index b6d853ce324e..e04bab52cc11 100644 --- a/src/Components/Web.JS/dist/Release/blazor.server.js +++ b/src/Components/Web.JS/dist/Release/blazor.server.js @@ -12,4 +12,4 @@ var r=n(50),o=n(51),i=n(52);function a(){return c.TYPED_ARRAY_SUPPORT?2147483647 * @author Feross Aboukhadijeh * @license MIT */ -function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(9))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(10)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(15).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(9))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(10),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(21);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(16),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(34).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(11),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t=o().encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t=o().encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o().encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o().encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}();n.d(t,"VERSION",function(){return u}),n.d(t,"MessagePackHubProtocol",function(){return c});var u="0.0.0-DEV_BUILD"}]); \ No newline at end of file +function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(9))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(10)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(15).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(9))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(10),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(21);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(16),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(34).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(11),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t=o().encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t=o().encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]);return a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o().encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o().encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}();n.d(t,"VERSION",function(){return u}),n.d(t,"MessagePackHubProtocol",function(){return c});var u="5.0.0-dev"}]); \ No newline at end of file diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs index c66c3c04e0e9..75ed809b6d97 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs @@ -8,7 +8,7 @@ public partial class AddressInUseException : System.InvalidOperationException public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } - public abstract partial class BaseConnectionContext + public abstract partial class BaseConnectionContext : System.IAsyncDisposable { protected BaseConnectionContext() { } public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } @@ -159,15 +159,10 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } - public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); - public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext - { - protected StreamContext() { } - public abstract string StreamId { get; } - } [System.FlagsAttribute] public enum TransferFormat { diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index c66c3c04e0e9..75ed809b6d97 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -8,7 +8,7 @@ public partial class AddressInUseException : System.InvalidOperationException public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } - public abstract partial class BaseConnectionContext + public abstract partial class BaseConnectionContext : System.IAsyncDisposable { protected BaseConnectionContext() { } public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } @@ -159,15 +159,10 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } - public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); - public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext - { - protected StreamContext() { } - public abstract string StreamId { get; } - } [System.FlagsAttribute] public enum TransferFormat { diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs index c66c3c04e0e9..75ed809b6d97 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs @@ -8,7 +8,7 @@ public partial class AddressInUseException : System.InvalidOperationException public AddressInUseException(string message) { } public AddressInUseException(string message, System.Exception inner) { } } - public abstract partial class BaseConnectionContext + public abstract partial class BaseConnectionContext : System.IAsyncDisposable { protected BaseConnectionContext() { } public virtual System.Threading.CancellationToken ConnectionClosed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } @@ -159,15 +159,10 @@ public MultiplexedConnectionBuilder(System.IServiceProvider applicationServices) public abstract partial class MultiplexedConnectionContext : Microsoft.AspNetCore.Connections.BaseConnectionContext, System.IAsyncDisposable { protected MultiplexedConnectionContext() { } - public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask AcceptAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Threading.Tasks.ValueTask ConnectAsync(Microsoft.AspNetCore.Http.Features.IFeatureCollection features = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } public delegate System.Threading.Tasks.Task MultiplexedConnectionDelegate(Microsoft.AspNetCore.Connections.MultiplexedConnectionContext connection); - public abstract partial class StreamContext : Microsoft.AspNetCore.Connections.ConnectionContext - { - protected StreamContext() { } - public abstract string StreamId { get; } - } [System.FlagsAttribute] public enum TransferFormat { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 48e8950cee5a..ce586bccedd5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -40,13 +40,13 @@ internal abstract class Http3Stream : HttpProtocol, IHttpHeadersHandler, IThread private readonly IProtocolErrorCodeFeature _errorCodeFeature; private readonly IStreamIdFeature _streamIdFeature; private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); - private RequestHeaderParsingState _requestHeaderParsingState; + protected RequestHeaderParsingState _requestHeaderParsingState; private PseudoHeaderFields _parsedPseudoHeaderFields; private bool _isMethodConnect; private readonly Http3Connection _http3Connection; private bool _receivedHeaders; - private Task _appTask = Task.CompletedTask; + private TaskCompletionSource _appCompleted; public Pipe RequestBodyPipe { get; } @@ -303,6 +303,13 @@ public void OnInputOrOutputCompleted() Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), Http3ErrorCode.NoError); } + protected override void OnRequestProcessingEnded() + { + Debug.Assert(_appCompleted != null); + + _appCompleted.SetResult(new object()); + } + private bool TryClose() { if (Interlocked.Exchange(ref _isClosed, 1) == 0) @@ -371,7 +378,7 @@ public async Task ProcessRequestAsync(IHttpApplication appli await RequestBodyPipe.Writer.CompleteAsync(); // Make sure application func is completed before completing writer. - await _appTask; + await _appCompleted.Task; try { @@ -447,7 +454,10 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli _receivedHeaders = true; InputRemaining = HttpRequestHeaders.ContentLength; - _appTask = Task.Run(() => base.ProcessRequestsAsync(application)); + _appCompleted = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); + return Task.CompletedTask; } @@ -718,7 +728,7 @@ private Pipe CreateRequestBodyPipe(uint windowSize) /// public abstract void Execute(); - private enum RequestHeaderParsingState + protected enum RequestHeaderParsingState { Ready, PseudoHeaderFields, diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs index 4a43f88f56de..b0dc9e472926 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamOfT.cs @@ -17,7 +17,14 @@ public Http3Stream(IHttpApplication application, Http3Connection conne public override void Execute() { - _ = ProcessRequestAsync(_application); + if (_requestHeaderParsingState == Http3Stream.RequestHeaderParsingState.Ready) + { + _ = ProcessRequestAsync(_application); + } + else + { + _ = base.ProcessRequestsAsync(_application); + } } // Pooled Host context From 8b2723ae832a7325ab53a6c013584269d3a298ef Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 24 Feb 2020 15:33:57 -0800 Subject: [PATCH 27/27] Check for null --- src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index ce586bccedd5..01c0234fe6fb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -378,7 +378,10 @@ public async Task ProcessRequestAsync(IHttpApplication appli await RequestBodyPipe.Writer.CompleteAsync(); // Make sure application func is completed before completing writer. - await _appCompleted.Task; + if (_appCompleted != null) + { + await _appCompleted.Task; + } try {