diff options
author | James Newton-King <james@newtonking.com> | 2022-07-12 12:11:20 +0300 |
---|---|---|
committer | James Newton-King <james@newtonking.com> | 2022-07-21 04:22:30 +0300 |
commit | 024e72fd9eba0a908e9b8aeb38e38042da03c0ba (patch) | |
tree | c6a6b7c6146a7c427306d1aba03e9c444aec5e4b | |
parent | c10f4c4484ace16981c4a397fb18bfdcd905367b (diff) |
HTTP/3: Avoid per-request cancellation token allocationsjamesnk/http3-completefeature
11 files changed, 248 insertions, 141 deletions
diff --git a/src/Servers/Connections.Abstractions/src/Features/IStreamClosedFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IStreamClosedFeature.cs new file mode 100644 index 0000000000..8fe03352ba --- /dev/null +++ b/src/Servers/Connections.Abstractions/src/Features/IStreamClosedFeature.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using System; + +namespace Microsoft.AspNetCore.Connections.Features; + +/// <summary> +/// Represents the close action for a stream. +/// </summary> +public interface IStreamClosedFeature +{ + /// <summary> + /// Registers a callback to be invoked when a stream is closed. + /// If the stream is already in a closed state, the callback will be run immediately. + /// </summary> + /// <param name="callback">The callback to invoke after the stream is closed.</param> + /// <param name="state">The state to pass into the callback.</param> + void OnClosed(Action<object?> callback, object? state); +} diff --git a/src/Servers/Connections.Abstractions/src/PublicAPI.Unshipped.txt b/src/Servers/Connections.Abstractions/src/PublicAPI.Unshipped.txt index 7dc5c58110..553973838a 100644 --- a/src/Servers/Connections.Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/Servers/Connections.Abstractions/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature +Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs index 38b083580e..adf2080c06 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -23,11 +23,13 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem private readonly Http3StreamContext _context; private readonly Http3PeerSettings _serverPeerSettings; private readonly IStreamIdFeature _streamIdFeature; + private readonly IStreamClosedFeature _streamClosedFeature; private readonly IProtocolErrorCodeFeature _errorCodeFeature; private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); private volatile int _isClosed; private long _headerType; private int _gracefulCloseInitiator; + private bool _connectionClosed; private bool _haveReceivedSettingsFrame; @@ -39,6 +41,7 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem _context = context; _serverPeerSettings = context.ServerPeerSettings; _streamIdFeature = context.ConnectionFeatures.GetRequiredFeature<IStreamIdFeature>(); + _streamClosedFeature = context.ConnectionFeatures.GetRequiredFeature<IStreamClosedFeature>(); _errorCodeFeature = context.ConnectionFeatures.GetRequiredFeature<IProtocolErrorCodeFeature>(); _headerType = headerType ?? -1; @@ -57,6 +60,7 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem private void OnStreamClosed() { Abort(new ConnectionAbortedException("HTTP_CLOSED_CRITICAL_STREAM"), Http3ErrorCode.InternalError); + _connectionClosed = true; } public PipeReader Input => _context.Transport.Input; @@ -237,7 +241,7 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem if (result.IsCompleted) { - if (!_context.StreamContext.ConnectionClosed.IsCancellationRequested) + if (!_connectionClosed) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-6.2.1-2 throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamClientClosedInbound, Http3ErrorCode.ClosedCriticalStream); @@ -298,7 +302,11 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem } _haveReceivedSettingsFrame = true; - using var closedRegistration = _context.StreamContext.ConnectionClosed.Register(state => ((Http3ControlStream)state!).OnStreamClosed(), this); + _streamClosedFeature.OnClosed(static state => + { + var stream = (Http3ControlStream)state!; + stream.OnStreamClosed(); + }, this); while (true) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 5f3a7dc23d..1ee524277e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -46,6 +46,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS private IProtocolErrorCodeFeature _errorCodeFeature = default!; private IStreamIdFeature _streamIdFeature = default!; private IStreamAbortFeature _streamAbortFeature = default!; + private IStreamClosedFeature _streamClosedFeature = default!; private PseudoHeaderFields _parsedPseudoHeaderFields; private StreamCompletionFlags _completionState; private int _isClosed; @@ -88,6 +89,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS _errorCodeFeature = _context.ConnectionFeatures.GetRequiredFeature<IProtocolErrorCodeFeature>(); _streamIdFeature = _context.ConnectionFeatures.GetRequiredFeature<IStreamIdFeature>(); _streamAbortFeature = _context.ConnectionFeatures.GetRequiredFeature<IStreamAbortFeature>(); + _streamClosedFeature = _context.ConnectionFeatures.GetRequiredFeature<IStreamClosedFeature>(); _appCompletedTaskSource.Reset(); _isClosed = 0; @@ -144,7 +146,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS { lock (_completionLock) { - if (IsCompleted) + if (IsCompleted || IsAborted) { return; } @@ -573,6 +575,28 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS { Exception? error = null; + // With HTTP/3 the write-side of the stream can be aborted by the client after the server + // has finished reading incoming content. That means errors can happen after the Input loop + // has finished reading. + // + // To get notification of request aborted we register to the stream closing or complete. + // It will notify this type that the client has aborted the request and Kestrel will complete + // pipes and cancel the HttpContext.RequestAborted token. + _streamClosedFeature.OnClosed(static s => + { + var stream = (Http3Stream)s!; + + if (!stream.IsCompleted) + { + // An error code value other than -1 indicates a value was set and the request didn't gracefully complete. + var errorCode = stream._errorCodeFeature.Error; + if (errorCode >= 0) + { + stream.AbortCore(new IOException(CoreStrings.HttpStreamResetByClient), (Http3ErrorCode)errorCode); + } + } + }, this); + try { while (_isClosed == 0) @@ -643,44 +667,9 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS ? new ValueTask(_appCompletedTaskSource, _appCompletedTaskSource.Version) : ValueTask.CompletedTask; - if (!appCompletedTask.IsCompletedSuccessfully) - { - // At this point in the stream's read-side is complete. However, with HTTP/3 - // the write-side of the stream can still be aborted by the client on request - // aborted. - // - // To get notification of request aborted we register to connection closed - // token. It will notify this type that the client has aborted the request - // and Kestrel will complete pipes and cancel the RequestAborted token. - // - // Only subscribe to this event after the stream's read-side is complete to - // avoid interactions between reading that is in-progress and an abort. - // This means while reading, read-side abort will handle getting abort notifications. - // - // We don't need to hang on to the CancellationTokenRegistration from register. - // The CTS is cleaned up in StreamContext.DisposeAsync. - // - // TODO: Consider a better way to provide this notification. For perf we want to - // make the ConnectionClosed CTS pay-for-play, and change this event to use - // something that is more lightweight than a CTS. - _context.StreamContext.ConnectionClosed.Register(static s => - { - var stream = (Http3Stream)s!; - - if (!stream.IsCompleted) - { - // An error code value other than -1 indicates a value was set and the request didn't gracefully complete. - var errorCode = stream._errorCodeFeature.Error; - if (errorCode >= 0) - { - stream.AbortCore(new IOException(CoreStrings.HttpStreamResetByClient), (Http3ErrorCode)errorCode); - } - } - }, this); - - // Make sure application func is completed before completing writer. - await appCompletedTask; - } + // At this point, assuming an error wasn't thrown, the stream's read-side is complete. + // Make sure application func is completed before completing writer. + await appCompletedTask; try { diff --git a/src/Servers/Kestrel/Core/test/Http3/Http3HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/Http3/Http3HttpProtocolFeatureCollectionTests.cs index 4cca5d8890..ba9d460474 100644 --- a/src/Servers/Kestrel/Core/test/Http3/Http3HttpProtocolFeatureCollectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http3/Http3HttpProtocolFeatureCollectionTests.cs @@ -67,7 +67,7 @@ public class Http3HttpProtocolFeatureCollectionTests } } - private class TestConnectionFeatures : IProtocolErrorCodeFeature, IStreamIdFeature, IStreamAbortFeature + private class TestConnectionFeatures : IProtocolErrorCodeFeature, IStreamIdFeature, IStreamAbortFeature, IStreamClosedFeature { public TestConnectionFeatures() { @@ -75,6 +75,7 @@ public class Http3HttpProtocolFeatureCollectionTests featureCollection.Set<IProtocolErrorCodeFeature>(this); featureCollection.Set<IStreamIdFeature>(this); featureCollection.Set<IStreamAbortFeature>(this); + featureCollection.Set<IStreamClosedFeature>(this); FeatureCollection = featureCollection; } @@ -92,5 +93,10 @@ public class Http3HttpProtocolFeatureCollectionTests { throw new NotImplementedException(); } + + void IStreamClosedFeature.OnClosed(Action<object> callback, object state) + { + throw new NotImplementedException(); + } } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.FeatureCollection.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.FeatureCollection.cs index b3728699f0..ed0807aedf 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.FeatureCollection.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.FeatureCollection.cs @@ -7,10 +7,19 @@ using Microsoft.AspNetCore.Connections.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal; -internal sealed partial class QuicStreamContext : IPersistentStateFeature, IStreamDirectionFeature, IProtocolErrorCodeFeature, IStreamIdFeature, IStreamAbortFeature +internal sealed partial class QuicStreamContext : + IPersistentStateFeature, + IStreamDirectionFeature, + IProtocolErrorCodeFeature, + IStreamIdFeature, + IStreamAbortFeature, + IStreamClosedFeature { + private readonly record struct CloseAction(Action<object?> Callback, object? State); + private IDictionary<object, object?>? _persistentState; private long? _error; + private List<CloseAction>? _onClosed; public bool CanRead { get; private set; } public bool CanWrite { get; private set; } @@ -72,6 +81,25 @@ internal sealed partial class QuicStreamContext : IPersistentStateFeature, IStre } } + void IStreamClosedFeature.OnClosed(Action<object?> callback, object? state) + { + lock (_shutdownLock) + { + if (!_streamClosed) + { + if (_onClosed == null) + { + _onClosed = new List<CloseAction>(); + } + _onClosed.Add(new CloseAction(callback, state)); + return; + } + } + + // Stream has already closed. Execute callback inline. + callback(state); + } + private void InitializeFeatures() { _currentIPersistentStateFeature = this; @@ -79,5 +107,6 @@ internal sealed partial class QuicStreamContext : IPersistentStateFeature, IStre _currentIProtocolErrorCodeFeature = this; _currentIStreamIdFeature = this; _currentIStreamAbortFeature = this; + _currentIStreamClosedFeature = this; } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs index 8944301048..d725385523 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -29,7 +29,7 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I private readonly CompletionPipeReader _transportPipeReader; private readonly CompletionPipeWriter _transportPipeWriter; private readonly ILogger _log; - private CancellationTokenSource _streamClosedTokenSource = default!; + private CancellationTokenSource? _streamClosedTokenSource; private string? _connectionId; private const int MinAllocBufferSize = 4096; private volatile Exception? _shutdownReadReason; @@ -39,7 +39,6 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I private bool _streamClosed; private bool _serverAborted; private bool _clientAbort; - private TaskCompletionSource _waitForConnectionClosedTcs = default!; private readonly object _shutdownLock = new object(); public QuicStreamContext(QuicConnectionContext connection, QuicTransportContext context) @@ -82,12 +81,8 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I _stream = stream; - if (!(_streamClosedTokenSource?.TryReset() ?? false)) - { - _streamClosedTokenSource = new CancellationTokenSource(); - } - - ConnectionClosed = _streamClosedTokenSource.Token; + _streamClosedTokenSource = null; + _onClosed?.Clear(); InitializeFeatures(); @@ -109,8 +104,6 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I _streamClosed = false; _serverAborted = false; _clientAbort = false; - // TODO - resetable TCS - _waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); // Only reset pipes if the stream has been reused. if (CanReuse) @@ -122,6 +115,20 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I CanReuse = false; } + public override CancellationToken ConnectionClosed + { + get + { + // Allocate CTS only if requested. + if (_streamClosedTokenSource == null) + { + _streamClosedTokenSource = new CancellationTokenSource(); + } + return _streamClosedTokenSource.Token; + } + set => throw new NotSupportedException(); + } + public override string ConnectionId { get => _connectionId ??= StringUtilities.ConcatAsHexSuffix(_connection.ConnectionId, ':', (uint)StreamId); @@ -162,7 +169,7 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I await receiveTask; await sendTask; - await FireStreamClosedAsync(); + FireStreamClosed(); } catch (Exception ex) { @@ -311,30 +318,39 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I return _shutdownReadReason ?? _shutdownReason ?? error; } - private Task FireStreamClosedAsync() + private void FireStreamClosed() { // Guard against scheduling this multiple times - if (_streamClosed) + lock (_shutdownLock) { - return Task.CompletedTask; + if (_streamClosed) + { + return; + } + + _streamClosed = true; } - _streamClosed = true; + var onClosed = _onClosed; - ThreadPool.UnsafeQueueUserWorkItem(state => + if (onClosed != null) { - state.CancelConnectionClosedToken(); - - state._waitForConnectionClosedTcs.TrySetResult(); - }, - this, - preferLocal: false); + foreach (var closeAction in onClosed) + { + closeAction.Callback(closeAction.State); + } + } - return _waitForConnectionClosedTcs.Task; + if (_streamClosedTokenSource != null) + { + CancelConnectionClosedToken(); + } } private void CancelConnectionClosedToken() { + Debug.Assert(_streamClosedTokenSource != null); + try { _streamClosedTokenSource.Cancel(); @@ -567,6 +583,6 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I // Called when the stream is no longer reused. public void DisposeCore() { - _streamClosedTokenSource.Dispose(); + _streamClosedTokenSource?.Dispose(); } } diff --git a/src/Servers/Kestrel/shared/TransportConnection.Generated.cs b/src/Servers/Kestrel/shared/TransportConnection.Generated.cs index b530dc8c15..090fa212e3 100644 --- a/src/Servers/Kestrel/shared/TransportConnection.Generated.cs +++ b/src/Servers/Kestrel/shared/TransportConnection.Generated.cs @@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Connections internal protected IStreamDirectionFeature? _currentIStreamDirectionFeature; internal protected IStreamIdFeature? _currentIStreamIdFeature; internal protected IStreamAbortFeature? _currentIStreamAbortFeature; + internal protected IStreamClosedFeature? _currentIStreamClosedFeature; private int _featureRevision; @@ -53,6 +54,7 @@ namespace Microsoft.AspNetCore.Connections _currentIStreamDirectionFeature = null; _currentIStreamIdFeature = null; _currentIStreamAbortFeature = null; + _currentIStreamClosedFeature = null; } // Internal for testing @@ -168,6 +170,10 @@ namespace Microsoft.AspNetCore.Connections { feature = _currentIStreamAbortFeature; } + else if (key == typeof(IStreamClosedFeature)) + { + feature = _currentIStreamClosedFeature; + } else if (MaybeExtra != null) { feature = ExtraFeatureGet(key); @@ -224,6 +230,10 @@ namespace Microsoft.AspNetCore.Connections { _currentIStreamAbortFeature = (IStreamAbortFeature?)value; } + else if (key == typeof(IStreamClosedFeature)) + { + _currentIStreamClosedFeature = (IStreamClosedFeature?)value; + } else { ExtraFeatureSet(key, value); @@ -282,6 +292,10 @@ namespace Microsoft.AspNetCore.Connections { feature = Unsafe.As<IStreamAbortFeature?, TFeature?>(ref _currentIStreamAbortFeature); } + else if (typeof(TFeature) == typeof(IStreamClosedFeature)) + { + feature = Unsafe.As<IStreamClosedFeature?, TFeature?>(ref _currentIStreamClosedFeature); + } else if (MaybeExtra != null) { feature = (TFeature?)(ExtraFeatureGet(typeof(TFeature))); @@ -346,6 +360,10 @@ namespace Microsoft.AspNetCore.Connections { _currentIStreamAbortFeature = Unsafe.As<TFeature?, IStreamAbortFeature?>(ref feature); } + else if (typeof(TFeature) == typeof(IStreamClosedFeature)) + { + _currentIStreamClosedFeature = Unsafe.As<TFeature?, IStreamClosedFeature?>(ref feature); + } else { ExtraFeatureSet(typeof(TFeature), feature); @@ -398,6 +416,10 @@ namespace Microsoft.AspNetCore.Connections { yield return new KeyValuePair<Type, object>(typeof(IStreamAbortFeature), _currentIStreamAbortFeature); } + if (_currentIStreamClosedFeature != null) + { + yield return new KeyValuePair<Type, object>(typeof(IStreamClosedFeature), _currentIStreamClosedFeature); + } if (MaybeExtra != null) { diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 9923e06cf2..443a1eb5e0 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -1077,8 +1077,10 @@ internal class TestMultiplexedConnectionContext : MultiplexedConnectionContext, } } -internal class TestStreamContext : ConnectionContext, IStreamDirectionFeature, IStreamIdFeature, IProtocolErrorCodeFeature, IPersistentStateFeature, IStreamAbortFeature, IDisposable +internal class TestStreamContext : ConnectionContext, IStreamDirectionFeature, IStreamIdFeature, IProtocolErrorCodeFeature, IPersistentStateFeature, IStreamAbortFeature, IDisposable, IStreamClosedFeature { + private readonly record struct CloseAction(Action<object> Callback, object State); + private readonly Http3InMemory _testBase; internal DuplexPipePair _pair; @@ -1096,6 +1098,7 @@ internal class TestStreamContext : ConnectionContext, IStreamDirectionFeature, I private TaskCompletionSource _disposingTcs; private TaskCompletionSource _disposedTcs; internal long? _error; + private List<CloseAction> _onClosed; public TestStreamContext(bool canRead, bool canWrite, Http3InMemory testBase) { @@ -1143,6 +1146,7 @@ internal class TestStreamContext : ConnectionContext, IStreamDirectionFeature, I Features.Set<IStreamAbortFeature>(this); Features.Set<IProtocolErrorCodeFeature>(this); Features.Set<IPersistentStateFeature>(this); + Features.Set<IStreamClosedFeature>(this); StreamId = streamId; _testBase.Logger.LogInformation($"Initializing stream {streamId}"); @@ -1264,4 +1268,13 @@ internal class TestStreamContext : ConnectionContext, IStreamDirectionFeature, I { AbortWriteException = abortReason; } + + public void OnClosed(Action<object> callback, object state) + { + if (_onClosed == null) + { + _onClosed = new List<CloseAction>(); + } + _onClosed.Add(new CloseAction(callback, state)); + } } diff --git a/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs index 3880971c75..6d43d799af 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs @@ -9,51 +9,51 @@ public class HttpProtocolFeatureCollection { var alwaysFeatures = new[] { - "IHttpRequestFeature", - "IHttpResponseFeature", - "IHttpResponseBodyFeature", - "IRouteValuesFeature", - "IEndpointFeature", - "IServiceProvidersFeature", - "IHttpActivityFeature" - }; + "IHttpRequestFeature", + "IHttpResponseFeature", + "IHttpResponseBodyFeature", + "IRouteValuesFeature", + "IEndpointFeature", + "IServiceProvidersFeature", + "IHttpActivityFeature" + }; var commonFeatures = new[] { - "IItemsFeature", - "IQueryFeature", - "IRequestBodyPipeFeature", - "IFormFeature", - "IHttpAuthenticationFeature", - "IHttpRequestIdentifierFeature", - }; + "IItemsFeature", + "IQueryFeature", + "IRequestBodyPipeFeature", + "IFormFeature", + "IHttpAuthenticationFeature", + "IHttpRequestIdentifierFeature", + }; var sometimesFeatures = new[] { - "IHttpConnectionFeature", - "ISessionFeature", - "IResponseCookiesFeature", - "IHttpRequestTrailersFeature", - "IHttpResponseTrailersFeature", - "ITlsConnectionFeature", - "IHttpExtendedConnectFeature", - "IHttpUpgradeFeature", - "IHttpWebSocketFeature", - "IHttpWebTransportFeature", - "IBadRequestExceptionFeature" - }; + "IHttpConnectionFeature", + "ISessionFeature", + "IResponseCookiesFeature", + "IHttpRequestTrailersFeature", + "IHttpResponseTrailersFeature", + "ITlsConnectionFeature", + "IHttpExtendedConnectFeature", + "IHttpUpgradeFeature", + "IHttpWebSocketFeature", + "IHttpWebTransportFeature", + "IBadRequestExceptionFeature" + }; var maybeFeatures = new[] { - "IHttp2StreamIdFeature", - "IHttpRequestLifetimeFeature", - "IHttpMaxRequestBodySizeFeature", - "IHttpMinRequestBodyDataRateFeature", - "IHttpMinResponseDataRateFeature", - "IHttpBodyControlFeature", - "IHttpRequestBodyDetectionFeature", - "IHttpResetFeature", - "IPersistentStateFeature" - }; + "IHttp2StreamIdFeature", + "IHttpRequestLifetimeFeature", + "IHttpMaxRequestBodySizeFeature", + "IHttpMinRequestBodyDataRateFeature", + "IHttpMinResponseDataRateFeature", + "IHttpBodyControlFeature", + "IHttpRequestBodyDetectionFeature", + "IHttpResetFeature", + "IPersistentStateFeature" + }; var allFeatures = alwaysFeatures .Concat(commonFeatures) @@ -65,24 +65,24 @@ public class HttpProtocolFeatureCollection // See also: src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs var implementedFeatures = new[] { - "IHttpRequestFeature", - "IHttpResponseFeature", - "IHttpResponseBodyFeature", - "IRouteValuesFeature", - "IEndpointFeature", - "IHttpRequestIdentifierFeature", - "IHttpRequestTrailersFeature", - "IHttpExtendedConnectFeature", - "IHttpUpgradeFeature", - "IRequestBodyPipeFeature", - "IHttpConnectionFeature", - "IHttpRequestLifetimeFeature", - "IHttpBodyControlFeature", - "IHttpMaxRequestBodySizeFeature", - "IHttpRequestBodyDetectionFeature", - "IHttpWebTransportFeature", - "IBadRequestExceptionFeature" - }; + "IHttpRequestFeature", + "IHttpResponseFeature", + "IHttpResponseBodyFeature", + "IRouteValuesFeature", + "IEndpointFeature", + "IHttpRequestIdentifierFeature", + "IHttpRequestTrailersFeature", + "IHttpExtendedConnectFeature", + "IHttpUpgradeFeature", + "IRequestBodyPipeFeature", + "IHttpConnectionFeature", + "IHttpRequestLifetimeFeature", + "IHttpBodyControlFeature", + "IHttpMaxRequestBodySizeFeature", + "IHttpRequestBodyDetectionFeature", + "IHttpWebTransportFeature", + "IBadRequestExceptionFeature" + }; var usings = $@" using Microsoft.AspNetCore.Connections.Features; diff --git a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs index 08443507ab..056320c238 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/TransportConnectionFeatureCollection.cs @@ -12,27 +12,28 @@ public class TransportConnectionFeatureCollection var allFeatures = new[] { - "IConnectionIdFeature", - "IConnectionTransportFeature", - "IConnectionItemsFeature", - "IPersistentStateFeature", - "IMemoryPoolFeature", - "IConnectionLifetimeFeature", - "IConnectionSocketFeature", - "IProtocolErrorCodeFeature", - "IStreamDirectionFeature", - "IStreamIdFeature", - "IStreamAbortFeature" - }; + "IConnectionIdFeature", + "IConnectionTransportFeature", + "IConnectionItemsFeature", + "IPersistentStateFeature", + "IMemoryPoolFeature", + "IConnectionLifetimeFeature", + "IConnectionSocketFeature", + "IProtocolErrorCodeFeature", + "IStreamDirectionFeature", + "IStreamIdFeature", + "IStreamAbortFeature", + "IStreamClosedFeature" + }; var implementedFeatures = new[] { - "IConnectionIdFeature", - "IConnectionTransportFeature", - "IConnectionItemsFeature", - "IMemoryPoolFeature", - "IConnectionLifetimeFeature" - }; + "IConnectionIdFeature", + "IConnectionTransportFeature", + "IConnectionItemsFeature", + "IMemoryPoolFeature", + "IConnectionLifetimeFeature" + }; var usings = $@" using Microsoft.AspNetCore.Connections.Features; |