From 4dfb1a10d89db62efea7f119b23f91a96878b7ed Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 20 Jun 2019 11:24:56 +0200 Subject: [PATCH 1/3] Remove connection adapters and move things to middleware - Remove connection adapters from the public API surface (pubternal) and replace the existing adapters with connection middleware. - Updated the tests --- ...tCore.Server.Kestrel.Core.netcoreapp3.0.cs | 19 -- .../src/Adapter/Internal/AdaptedPipeline.cs | 173 ---------------- .../Internal/ConnectionAdapterContext.cs | 26 --- .../Adapter/Internal/IAdaptedConnection.cs | 13 -- .../Adapter/Internal/IConnectionAdapter.cs | 13 -- .../Internal/LoggingConnectionAdapter.cs | 47 ----- .../Core/src/Internal/AddressBinder.cs | 5 +- .../Core/src/Internal/ConnectionDispatcher.cs | 2 +- .../Core/src/Internal/HttpConnection.cs | 117 +---------- .../HttpConnectionBuilderExtensions.cs | 8 +- .../src/Internal/HttpConnectionContext.cs | 2 - .../src/Internal/HttpConnectionMiddleware.cs | 12 +- .../src/Internal/HttpsConnectionMiddleware.cs | 89 ++++----- src/Servers/Kestrel/Core/src/KestrelServer.cs | 2 +- src/Servers/Kestrel/Core/src/ListenOptions.cs | 24 +-- .../Core/src/LocalhostListenOptions.cs | 2 - .../Internal/DuplexPipeStream.cs} | 6 +- .../Internal/DuplexPipeStreamAdapter.cs | 47 +++++ .../Internal/LoggingConnectionMiddleware.cs | 50 +++++ .../Internal/LoggingStream.cs | 2 +- ...istenOptionsConnectionLoggingExtensions.cs | 6 +- .../Kestrel/Core/test/ListenOptionsTests.cs | 4 +- .../Kestrel/Core/test/PipeOptionsTests.cs | 52 ----- .../test/LibuvTransportTests.cs | 2 +- .../Kestrel/samples/Http2SampleApp/Program.cs | 51 ++--- .../test/PassThroughConnectionAdapter.cs | 170 +++++----------- .../FunctionalTests/ConnectionAdapterTests.cs | 19 +- .../MaxRequestBufferSizeTests.cs | 2 +- .../test/FunctionalTests/RequestTests.cs | 16 +- .../test/FunctionalTests/ResponseTests.cs | 15 +- .../ConnectionAdapterTests.cs | 184 +++++++++--------- .../Http2/PipeReaderFactory.cs | 6 +- .../LoggingConnectionAdapterTests.cs | 2 +- .../ResponseDrainingTests.cs | 7 +- .../TestTransport/InMemoryConnection.cs | 4 +- 35 files changed, 342 insertions(+), 857 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs delete mode 100644 src/Servers/Kestrel/Core/src/Adapter/Internal/ConnectionAdapterContext.cs delete mode 100644 src/Servers/Kestrel/Core/src/Adapter/Internal/IAdaptedConnection.cs delete mode 100644 src/Servers/Kestrel/Core/src/Adapter/Internal/IConnectionAdapter.cs delete mode 100644 src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingConnectionAdapter.cs rename src/Servers/Kestrel/Core/src/{Adapter/Internal/RawStream.cs => Middleware/Internal/DuplexPipeStream.cs} (97%) create mode 100644 src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs create mode 100644 src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs rename src/Servers/Kestrel/Core/src/{Adapter => Middleware}/Internal/LoggingStream.cs (99%) rename src/Servers/Kestrel/Core/src/{Adapter => Middleware}/ListenOptionsConnectionLoggingExtensions.cs (85%) delete mode 100644 src/Servers/Kestrel/Core/test/PipeOptionsTests.cs diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs index c1bbe28155ae..e301c44ccd27 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs @@ -143,7 +143,6 @@ public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectio { internal ListenOptions() { } public System.IServiceProvider ApplicationServices { get { throw null; } } - public System.Collections.Generic.List ConnectionAdapters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public ulong FileHandle { get { throw null; } } public System.Net.IPEndPoint IPEndPoint { get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions KestrelServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } @@ -160,24 +159,6 @@ public MinDataRate(double bytesPerSecond, System.TimeSpan gracePeriod) { } public System.TimeSpan GracePeriod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } } -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - public partial class ConnectionAdapterContext - { - internal ConnectionAdapterContext() { } - public System.IO.Stream ConnectionStream { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } - } - public partial interface IAdaptedConnection : System.IDisposable - { - System.IO.Stream ConnectionStream { get; } - } - public partial interface IConnectionAdapter - { - bool IsHttps { get; } - System.Threading.Tasks.Task OnConnectionAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal.ConnectionAdapterContext context); - } -} namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features { public partial interface IConnectionTimeoutFeature diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs deleted file mode 100644 index eafdef55c162..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs +++ /dev/null @@ -1,173 +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.IO; -using System.IO.Pipelines; -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.Adapter.Internal -{ - internal class AdaptedPipeline : IDuplexPipe - { - private readonly int _minAllocBufferSize; - - private Task _inputTask; - private Task _outputTask; - - public AdaptedPipeline(IDuplexPipe transport, - Pipe inputPipe, - Pipe outputPipe, - ILogger log, - int minAllocBufferSize) - { - TransportStream = new RawStream(transport.Input, transport.Output, throwOnCancelled: true); - Input = inputPipe; - Output = outputPipe; - Log = log; - _minAllocBufferSize = minAllocBufferSize; - } - - public RawStream TransportStream { get; } - - public Pipe Input { get; } - - public Pipe Output { get; } - - public ILogger Log { get; } - - PipeReader IDuplexPipe.Input => Input.Reader; - - PipeWriter IDuplexPipe.Output => Output.Writer; - - public void RunAsync(Stream stream) - { - _inputTask = ReadInputAsync(stream); - _outputTask = WriteOutputAsync(stream); - } - - public async Task CompleteAsync() - { - Output.Writer.Complete(); - Input.Reader.Complete(); - - if (_outputTask == null) - { - return; - } - - // Wait for the output task to complete, this ensures that we've copied - // the application data to the underlying stream - await _outputTask; - - // Cancel the underlying stream so that the input task yields - TransportStream.CancelPendingRead(); - - // The input task should yield now that we've cancelled it - await _inputTask; - } - - private async Task WriteOutputAsync(Stream stream) - { - try - { - if (stream == null) - { - return; - } - - while (true) - { - var result = await Output.Reader.ReadAsync(); - var buffer = result.Buffer; - - try - { - if (buffer.IsEmpty) - { - if (result.IsCompleted) - { - break; - } - await stream.FlushAsync(); - } - else if (buffer.IsSingleSegment) - { - await stream.WriteAsync(buffer.First); - } - else - { - foreach (var memory in buffer) - { - await stream.WriteAsync(memory); - } - } - } - finally - { - Output.Reader.AdvanceTo(buffer.End); - } - } - } - catch (Exception ex) - { - Log.LogError(0, ex, $"{nameof(AdaptedPipeline)}.{nameof(WriteOutputAsync)}"); - } - finally - { - Output.Reader.Complete(); - } - } - - private async Task ReadInputAsync(Stream stream) - { - Exception error = null; - - try - { - if (stream == null) - { - // REVIEW: Do we need an exception here? - return; - } - - while (true) - { - var outputBuffer = Input.Writer.GetMemory(_minAllocBufferSize); - var bytesRead = await stream.ReadAsync(outputBuffer); - Input.Writer.Advance(bytesRead); - - if (bytesRead == 0) - { - // FIN - break; - } - - var result = await Input.Writer.FlushAsync(); - - if (result.IsCompleted) - { - break; - } - } - } - catch (OperationCanceledException ex) - { - // Propagate the exception if it's ConnectionAbortedException - error = ex as ConnectionAbortedException; - } - catch (Exception ex) - { - // Don't rethrow the exception. It should be handled by the Pipeline consumer. - error = ex; - } - finally - { - Input.Writer.Complete(error); - } - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/ConnectionAdapterContext.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/ConnectionAdapterContext.cs deleted file mode 100644 index 3896e1cf85c4..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/ConnectionAdapterContext.cs +++ /dev/null @@ -1,26 +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.IO; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Http.Features; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - // Even though this only includes the non-adapted ConnectionStream currently, this is a context in case - // we want to add more connection metadata later. - public class ConnectionAdapterContext - { - internal ConnectionAdapterContext(ConnectionContext connectionContext, Stream connectionStream) - { - ConnectionContext = connectionContext; - ConnectionStream = connectionStream; - } - - internal ConnectionContext ConnectionContext { get; } - - public IFeatureCollection Features => ConnectionContext.Features; - - public Stream ConnectionStream { get; } - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/IAdaptedConnection.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/IAdaptedConnection.cs deleted file mode 100644 index 5960490e2bbb..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/IAdaptedConnection.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; -using System.IO; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - public interface IAdaptedConnection : IDisposable - { - Stream ConnectionStream { get; } - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/IConnectionAdapter.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/IConnectionAdapter.cs deleted file mode 100644 index e0249d55456a..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/IConnectionAdapter.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.Server.Kestrel.Core.Adapter.Internal -{ - public interface IConnectionAdapter - { - bool IsHttps { get; } - Task OnConnectionAsync(ConnectionAdapterContext context); - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingConnectionAdapter.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingConnectionAdapter.cs deleted file mode 100644 index c69aca5d62da..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingConnectionAdapter.cs +++ /dev/null @@ -1,47 +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.IO; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - internal class LoggingConnectionAdapter : IConnectionAdapter - { - private readonly ILogger _logger; - - public LoggingConnectionAdapter(ILogger logger) - { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; - } - - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) - { - return Task.FromResult( - new LoggingAdaptedConnection(context.ConnectionStream, _logger)); - } - - private class LoggingAdaptedConnection : IAdaptedConnection - { - public LoggingAdaptedConnection(Stream rawStream, ILogger logger) - { - ConnectionStream = new LoggingStream(rawStream, logger); - } - - public Stream ConnectionStream { get; } - - public void Dispose() - { - } - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs b/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs index 83520f36598d..70a306c3adda 100644 --- a/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs +++ b/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs @@ -171,8 +171,7 @@ public async Task BindAsync(AddressBindContext context) var httpsDefault = ParseAddress(Constants.DefaultServerHttpsAddress, out https); context.ServerOptions.ApplyEndpointDefaults(httpsDefault); - if (httpsDefault.ConnectionAdapters.Any(f => f.IsHttps) - || httpsDefault.TryUseHttps()) + if (httpsDefault.IsTls || httpsDefault.TryUseHttps()) { await httpsDefault.BindAsync(context).ConfigureAwait(false); context.Logger.LogDebug(CoreStrings.BindingToDefaultAddresses, @@ -255,7 +254,7 @@ public virtual async Task BindAsync(AddressBindContext context) var options = ParseAddress(address, out var https); context.ServerOptions.ApplyEndpointDefaults(options); - if (https && !options.ConnectionAdapters.Any(f => f.IsHttps)) + if (https && !options.IsTls) { options.UseHttps(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 4e354d811358..8670469ea5f8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -86,7 +86,7 @@ internal async Task Execute(KestrelConnection connection) } catch (Exception ex) { - Log.LogCritical(0, ex, $"{nameof(ConnectionDispatcher)}.{nameof(Execute)}() {connectionContext.ConnectionId}"); + Log.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 26a9d7b86348..0022748f34e3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -2,10 +2,7 @@ // 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.Diagnostics; -using System.IO; using System.IO.Pipelines; using System.Net; using System.Threading.Tasks; @@ -13,7 +10,6 @@ using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; @@ -30,9 +26,6 @@ internal class HttpConnection : ITimeoutHandler private readonly ISystemClock _systemClock; private readonly TimeoutControl _timeoutControl; - private IList _adaptedConnections; - private IDuplexPipe _adaptedTransport; - private readonly object _protocolSelectionLock = new object(); private ProtocolSelectionState _protocolSelectionState = ProtocolSelectionState.Initializing; private IRequestProcessor _requestProcessor; @@ -50,54 +43,12 @@ public HttpConnection(HttpConnectionContext context) public IPEndPoint LocalEndPoint => _context.LocalEndPoint; public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint; - private MemoryPool MemoryPool => _context.MemoryPool; - - // Internal for testing - internal PipeOptions AdaptedInputPipeOptions => new PipeOptions - ( - pool: MemoryPool, - readerScheduler: _context.ServiceContext.Scheduler, - writerScheduler: PipeScheduler.Inline, - pauseWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, - resumeWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, - useSynchronizationContext: false, - minimumSegmentSize: MemoryPool.GetMinimumSegmentSize() - ); - - internal PipeOptions AdaptedOutputPipeOptions => new PipeOptions - ( - pool: MemoryPool, - readerScheduler: PipeScheduler.Inline, - writerScheduler: PipeScheduler.Inline, - pauseWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxResponseBufferSize ?? 0, - resumeWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxResponseBufferSize ?? 0, - useSynchronizationContext: false, - minimumSegmentSize: MemoryPool.GetMinimumSegmentSize() - ); - private IKestrelTrace Log => _context.ServiceContext.Log; public async Task ProcessRequestsAsync(IHttpApplication httpApplication) { try { - AdaptedPipeline adaptedPipeline = null; - - // _adaptedTransport must be set prior to wiring up callbacks - // to allow the connection to be aborted prior to protocol selection. - _adaptedTransport = _context.Transport; - - if (_context.ConnectionAdapters.Count > 0) - { - adaptedPipeline = new AdaptedPipeline(_adaptedTransport, - new Pipe(AdaptedInputPipeOptions), - new Pipe(AdaptedOutputPipeOptions), - Log, - MemoryPool.GetMinimumAllocSize()); - - _adaptedTransport = adaptedPipeline; - } - // This feature should never be null in Kestrel var connectionHeartbeatFeature = _context.ConnectionFeatures.Get(); @@ -116,13 +67,6 @@ public async Task ProcessRequestsAsync(IHttpApplication http _context.ConnectionFeatures.Set(_timeoutControl); - if (adaptedPipeline != null) - { - // Stream can be null here and run async will close the connection in that case - var stream = await ApplyConnectionAdaptersAsync(adaptedPipeline.TransportStream); - adaptedPipeline.RunAsync(stream); - } - IRequestProcessor requestProcessor = null; lock (_protocolSelectionLock) @@ -130,7 +74,7 @@ public async Task ProcessRequestsAsync(IHttpApplication http // Ensure that the connection hasn't already been stopped. if (_protocolSelectionState == ProtocolSelectionState.Initializing) { - var derivedContext = CreateDerivedContext(_adaptedTransport); + var derivedContext = CreateDerivedContext(_context.Transport); switch (SelectProtocol()) { @@ -169,9 +113,6 @@ public async Task ProcessRequestsAsync(IHttpApplication http await requestProcessor.ProcessRequestsAsync(httpApplication); } } - - // Complete the pipeline after the method runs - await (adaptedPipeline?.CompleteAsync() ?? Task.CompletedTask); } } catch (Exception ex) @@ -180,8 +121,6 @@ public async Task ProcessRequestsAsync(IHttpApplication http } finally { - DisposeAdaptedConnections(); - if (_http1Connection?.IsUpgraded == true) { _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); @@ -234,7 +173,7 @@ private void StopProcessingNextRequest() switch (previousState) { case ProtocolSelectionState.Initializing: - CloseUninitializedConnection(new ConnectionAbortedException(CoreStrings.ServerShutdownDuringConnectionInitialization)); + _context.ConnectionContext.Abort(new ConnectionAbortedException(CoreStrings.ServerShutdownDuringConnectionInitialization)); break; case ProtocolSelectionState.Selected: _requestProcessor.StopProcessingNextRequest(); @@ -268,7 +207,7 @@ private void OnInputOrOutputCompleted() // ConnectionClosed callback is not wired up until after leaving the Initializing state. Debug.Assert(false); - CloseUninitializedConnection(new ConnectionAbortedException("HttpConnection.OnInputOrOutputCompleted() called while in the ProtocolSelectionState.Initializing state!?")); + _context.ConnectionContext.Abort(new ConnectionAbortedException("HttpConnection.OnInputOrOutputCompleted() called while in the ProtocolSelectionState.Initializing state!?")); break; case ProtocolSelectionState.Selected: _requestProcessor.OnInputOrOutputCompleted(); @@ -291,7 +230,7 @@ private void Abort(ConnectionAbortedException ex) switch (previousState) { case ProtocolSelectionState.Initializing: - CloseUninitializedConnection(ex); + _context.ConnectionContext.Abort(ex); break; case ProtocolSelectionState.Selected: _requestProcessor.Abort(ex); @@ -301,43 +240,6 @@ private void Abort(ConnectionAbortedException ex) } } - private async Task ApplyConnectionAdaptersAsync(RawStream stream) - { - var connectionAdapters = _context.ConnectionAdapters; - var adapterContext = new ConnectionAdapterContext(_context.ConnectionContext, stream); - _adaptedConnections = new List(connectionAdapters.Count); - - try - { - for (var i = 0; i < connectionAdapters.Count; i++) - { - var adaptedConnection = await connectionAdapters[i].OnConnectionAsync(adapterContext); - _adaptedConnections.Add(adaptedConnection); - adapterContext = new ConnectionAdapterContext(_context.ConnectionContext, adaptedConnection.ConnectionStream); - } - } - catch (Exception ex) - { - Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}."); - - return null; - } - - return adapterContext.ConnectionStream; - } - - private void DisposeAdaptedConnections() - { - var adaptedConnections = _adaptedConnections; - if (adaptedConnections != null) - { - for (var i = adaptedConnections.Count - 1; i >= 0; i--) - { - adaptedConnections[i].Dispose(); - } - } - } - private HttpProtocols SelectProtocol() { var hasTls = _context.ConnectionFeatures.Get() != null; @@ -388,17 +290,6 @@ private void Tick() _requestProcessor?.Tick(now); } - private void CloseUninitializedConnection(ConnectionAbortedException abortReason) - { - _context.ConnectionContext.Abort(abortReason); - - if (_context.ConnectionAdapters.Count > 0) - { - _adaptedTransport.Input.Complete(); - _adaptedTransport.Output.Complete(); - } - } - 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 diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs index a2ac0839b0da..e46a2c2a831e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { @@ -13,12 +12,7 @@ internal static class HttpConnectionBuilderExtensions { public static IConnectionBuilder UseHttpServer(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { - return builder.UseHttpServer(Array.Empty(), serviceContext, application, protocols); - } - - public static IConnectionBuilder UseHttpServer(this IConnectionBuilder builder, IList adapters, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) - { - var middleware = new HttpConnectionMiddleware(adapters, serviceContext, application, protocols); + var middleware = new HttpConnectionMiddleware(serviceContext, application, protocols); return builder.Use(next => { return middleware.OnConnectionAsync; diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs index 3198f6006b20..562b7bd1a965 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs @@ -7,7 +7,6 @@ using System.Net; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal @@ -19,7 +18,6 @@ internal class HttpConnectionContext public ConnectionContext ConnectionContext { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } - public IList ConnectionAdapters { get; set; } public MemoryPool MemoryPool { get; set; } public IPEndPoint LocalEndPoint { get; set; } public IPEndPoint RemoteEndPoint { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs index efae39ce71bb..c1c0aebc4728 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs @@ -1,38 +1,29 @@ // 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.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.Adapter.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { internal class HttpConnectionMiddleware { - private readonly IList _connectionAdapters; private readonly ServiceContext _serviceContext; private readonly IHttpApplication _application; private readonly HttpProtocols _protocols; - public HttpConnectionMiddleware(IList adapters, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) + public HttpConnectionMiddleware(ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { _serviceContext = serviceContext; _application = application; _protocols = protocols; - - // Keeping these around for now so progress can be made without updating tests - _connectionAdapters = adapters; } public Task OnConnectionAsync(ConnectionContext connectionContext) { - // We need the transport feature so that we can cancel the output reader that the transport is using - // This is a bit of a hack but it preserves the existing semantics var memoryPoolFeature = connectionContext.Features.Get(); var httpConnectionContext = new HttpConnectionContext @@ -43,7 +34,6 @@ public Task OnConnectionAsync(ConnectionContext connectionContext) ServiceContext = _serviceContext, ConnectionFeatures = connectionContext.Features, MemoryPool = memoryPoolFeature.MemoryPool, - ConnectionAdapters = _connectionAdapters, Transport = connectionContext.Transport }; diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs index 5cd60b720c10..41b06dff3735 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs @@ -6,6 +6,7 @@ using System.IO; using System.IO.Pipelines; using System.Net.Security; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; @@ -15,7 +16,6 @@ using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.Extensions.Logging; @@ -80,7 +80,6 @@ public Task OnConnectionAsync(ConnectionContext context) private async Task InnerOnConnectionAsync(ConnectionContext context) { - SslStream sslStream; bool certificateRequired; var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set(feature); @@ -89,40 +88,28 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) // TODO: Handle the cases where this can be null var memoryPoolFeature = context.Features.Get(); - var inputPipeOptions = new PipeOptions + var inputPipeOptions = new StreamPipeReaderOptions ( pool: memoryPoolFeature.MemoryPool, - readerScheduler: _options.Scheduler, - writerScheduler: PipeScheduler.Inline, - pauseWriterThreshold: _options.MaxInputBufferSize ?? 0, - resumeWriterThreshold: _options.MaxInputBufferSize / 2 ?? 0, - useSynchronizationContext: false, - minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() + bufferSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize(), + minimumReadSize: memoryPoolFeature.MemoryPool.GetMinimumAllocSize() ); - var outputPipeOptions = new PipeOptions + var outputPipeOptions = new StreamPipeWriterOptions ( - pool: memoryPoolFeature.MemoryPool, - readerScheduler: PipeScheduler.Inline, - writerScheduler: PipeScheduler.Inline, - pauseWriterThreshold: _options.MaxOutputBufferSize ?? 0, - resumeWriterThreshold: _options.MaxOutputBufferSize / 2 ?? 0, - useSynchronizationContext: false, - minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() + pool: memoryPoolFeature.MemoryPool ); - // TODO: eventually make SslDuplexStream : Stream, IDuplexPipe to avoid RawStream allocation and pipe allocations - var adaptedPipeline = new AdaptedPipeline(context.Transport, new Pipe(inputPipeOptions), new Pipe(outputPipeOptions), _logger, memoryPoolFeature.MemoryPool.GetMinimumAllocSize()); - var transportStream = adaptedPipeline.TransportStream; + SslDuplexPipe sslDuplexPipe = null; if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { - sslStream = new SslStream(transportStream); + sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); certificateRequired = false; } else { - sslStream = new SslStream(transportStream, + sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions, s => new SslStream(s, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { @@ -154,7 +141,7 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) } return true; - }); + })); certificateRequired = true; } @@ -170,7 +157,7 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) { selector = (sender, name) => { - context.Features.Set(sslStream); + context.Features.Set(sslDuplexPipe.Stream); var cert = _serverCertificateSelector(context, name); if (cert != null) { @@ -205,51 +192,43 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) _options.OnAuthenticate?.Invoke(context, sslOptions); - await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); + await sslDuplexPipe.Stream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); } catch (OperationCanceledException) { _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); - sslStream.Dispose(); + await sslDuplexPipe.Stream.DisposeAsync(); return; } catch (Exception ex) when (ex is IOException || ex is AuthenticationException) { _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); - sslStream.Dispose(); + await sslDuplexPipe.Stream.DisposeAsync(); return; } } - feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; + feature.ApplicationProtocol = sslDuplexPipe.Stream.NegotiatedApplicationProtocol.Protocol; context.Features.Set(feature); - feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); - feature.CipherAlgorithm = sslStream.CipherAlgorithm; - feature.CipherStrength = sslStream.CipherStrength; - feature.HashAlgorithm = sslStream.HashAlgorithm; - feature.HashStrength = sslStream.HashStrength; - feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; - feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; - feature.Protocol = sslStream.SslProtocol; + feature.ClientCertificate = ConvertToX509Certificate2(sslDuplexPipe.Stream.RemoteCertificate); + feature.CipherAlgorithm = sslDuplexPipe.Stream.CipherAlgorithm; + feature.CipherStrength = sslDuplexPipe.Stream.CipherStrength; + feature.HashAlgorithm = sslDuplexPipe.Stream.HashAlgorithm; + feature.HashStrength = sslDuplexPipe.Stream.HashStrength; + feature.KeyExchangeAlgorithm = sslDuplexPipe.Stream.KeyExchangeAlgorithm; + feature.KeyExchangeStrength = sslDuplexPipe.Stream.KeyExchangeStrength; + feature.Protocol = sslDuplexPipe.Stream.SslProtocol; var original = context.Transport; try { - context.Transport = adaptedPipeline; + context.Transport = sslDuplexPipe; - using (sslStream) + // Disposing the stream will dispose the sslDuplexPipe + await using (sslDuplexPipe.Stream) { - try - { - adaptedPipeline.RunAsync(sslStream); - - await _next(context); - } - finally - { - await adaptedPipeline.CompleteAsync(); - } + await _next(context); } } finally @@ -281,5 +260,19 @@ private static X509Certificate2 ConvertToX509Certificate2(X509Certificate certif return new X509Certificate2(certificate); } + + private class SslDuplexPipe : DuplexPipeStreamAdapter + { + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) + : this(transport, readerOptions, writerOptions, s => new SslStream(s)) + { + + } + + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : + base(transport, readerOptions, writerOptions, factory) + { + } + } } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 868594b8f45e..6eb5ada99de4 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -124,7 +124,7 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions options) { // Add the HTTP middleware as the terminal connection middleware - options.UseHttpServer(options.ConnectionAdapters, ServiceContext, application, options.Protocols); + options.UseHttpServer(ServiceContext, application, options.Protocols); var connectionDelegate = options.Build(); diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index f3153bae1477..14e483c403dd 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core @@ -63,7 +61,7 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType) public ulong FileHandle => (EndPoint as FileHandleEndPoint)?.FileHandle ?? 0; /// - /// Enables an to resolve and use services registered by the application during startup. + /// Enables connection middleware to resolve and use services registered by the application during startup. /// Only set if accessed from the callback of a Listen* method. /// public KestrelServerOptions KestrelServerOptions { get; internal set; } @@ -74,19 +72,6 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType) /// Defaults to HTTP/1.x and HTTP/2. public HttpProtocols Protocols { get; set; } = HttpProtocols.Http1AndHttp2; - /// - /// Gets the that allows each connection - /// to be intercepted and transformed. - /// Configured by the UseHttps() and - /// extension methods. - /// - /// - /// Defaults to empty. - /// -#pragma warning disable PUB0001 // Pubternal type in public API - public List ConnectionAdapters { get; } = new List(); -#pragma warning restore PUB0001 // Pubternal type in public API - public IServiceProvider ApplicationServices => KestrelServerOptions?.ApplicationServices; internal string Scheme @@ -125,6 +110,13 @@ internal virtual string GetDisplayName() public override string ToString() => GetDisplayName(); + /// + /// Adds a middleware delegate to the connection pipeline. + /// Configured by the UseHttps() and + /// extension methods. + /// + /// The middleware delegate. + /// The . public IConnectionBuilder Use(Func middleware) { _middleware.Add(middleware); diff --git a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs index ba0a82736d1b..1c465cb14720 100644 --- a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; @@ -78,7 +77,6 @@ internal ListenOptions Clone(IPAddress address) }; options._middleware.AddRange(_middleware); - options.ConnectionAdapters.AddRange(ConnectionAdapters); return options; } } diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/RawStream.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs similarity index 97% rename from src/Servers/Kestrel/Core/src/Adapter/Internal/RawStream.cs rename to src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs index 726dcaa3292b..1cdde1631340 100644 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/RawStream.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs @@ -8,16 +8,16 @@ using System.Threading.Tasks; using System.Buffers; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - internal sealed class RawStream : Stream + internal class DuplexPipeStream : Stream { private readonly PipeReader _input; private readonly PipeWriter _output; private readonly bool _throwOnCancelled; private volatile bool _cancelCalled; - public RawStream(PipeReader input, PipeWriter output, bool throwOnCancelled = false) + public DuplexPipeStream(PipeReader input, PipeWriter output, bool throwOnCancelled = false) { _input = input; _output = output; diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs new file mode 100644 index 000000000000..b387af0f7f63 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.IO.Pipelines; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + /// + /// A helper for wrapping a Stream decorator from an . + /// + /// + internal class DuplexPipeStreamAdapter : DuplexPipeStream, IDuplexPipe where TStream : Stream + { + public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func createStream) : + this(duplexPipe, new StreamPipeReaderOptions(), new StreamPipeWriterOptions(), createStream) + { + + } + + public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func createStream) : base(duplexPipe.Input, duplexPipe.Output) + { + Stream = createStream(this); + Input = PipeReader.Create(Stream, readerOptions); + Output = PipeWriter.Create(Stream, writerOptions); + } + + public TStream Stream { get; } + + public PipeReader Input { get; } + + public PipeWriter Output { get; } + + protected override void Dispose(bool disposing) + { + Input.Complete(); + Output.Complete(); + base.Dispose(disposing); + } + + public override ValueTask DisposeAsync() + { + Input.Complete(); + Output.Complete(); + return base.DisposeAsync(); + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs new file mode 100644 index 000000000000..0df816b827d6 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs @@ -0,0 +1,50 @@ +// 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.IO.Pipelines; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class LoggingConnectionMiddleware + { + private readonly ConnectionDelegate _next; + private readonly ILogger _logger; + + public LoggingConnectionMiddleware(ConnectionDelegate next, ILogger logger) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task OnConnectionAsync(ConnectionContext context) + { + var oldTranspot = context.Transport; + + try + { + await using (var loggingDuplexPipe = new LoggingDuplexPipe(context.Transport, _logger)) + { + context.Transport = loggingDuplexPipe; + + await _next(context); + } + } + finally + { + context.Transport = oldTranspot; + } + } + + private class LoggingDuplexPipe : DuplexPipeStreamAdapter + { + public LoggingDuplexPipe(IDuplexPipe transport, ILogger logger) : + base(transport, stream => new LoggingStream(stream, logger)) + { + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingStream.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs similarity index 99% rename from src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingStream.cs rename to src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs index 71ed417a1177..e3fdec3f8180 100644 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingStream.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { internal sealed class LoggingStream : Stream { diff --git a/src/Servers/Kestrel/Core/src/Adapter/ListenOptionsConnectionLoggingExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/ListenOptionsConnectionLoggingExtensions.cs similarity index 85% rename from src/Servers/Kestrel/Core/src/Adapter/ListenOptionsConnectionLoggingExtensions.cs rename to src/Servers/Kestrel/Core/src/Middleware/ListenOptionsConnectionLoggingExtensions.cs index ef95004a3074..de4f8a62a554 100644 --- a/src/Servers/Kestrel/Core/src/Adapter/ListenOptionsConnectionLoggingExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/ListenOptionsConnectionLoggingExtensions.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -30,8 +30,8 @@ public static ListenOptions UseConnectionLogging(this ListenOptions listenOption public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions, string loggerName) { var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService(); - var logger = loggerName == null ? loggerFactory.CreateLogger() : loggerFactory.CreateLogger(loggerName); - listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(logger)); + var logger = loggerName == null ? loggerFactory.CreateLogger() : loggerFactory.CreateLogger(loggerName); + listenOptions.Use(next => new LoggingConnectionMiddleware(next, logger).OnConnectionAsync); return listenOptions; } } diff --git a/src/Servers/Kestrel/Core/test/ListenOptionsTests.cs b/src/Servers/Kestrel/Core/test/ListenOptionsTests.cs index 998d0a34a43a..3eb462679639 100644 --- a/src/Servers/Kestrel/Core/test/ListenOptionsTests.cs +++ b/src/Servers/Kestrel/Core/test/ListenOptionsTests.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.Net; @@ -22,7 +22,6 @@ public void ProtocolsDefault() public void LocalHostListenOptionsClonesConnectionMiddleware() { var localhostListenOptions = new LocalhostListenOptions(1004); - localhostListenOptions.ConnectionAdapters.Add(new PassThroughConnectionAdapter()); var serviceProvider = new ServiceCollection().BuildServiceProvider(); localhostListenOptions.KestrelServerOptions = new KestrelServerOptions { @@ -45,7 +44,6 @@ public void LocalHostListenOptionsClonesConnectionMiddleware() Assert.NotNull(clone.KestrelServerOptions); Assert.NotNull(serviceProvider); Assert.Same(serviceProvider, clone.ApplicationServices); - Assert.Single(clone.ConnectionAdapters); } } } diff --git a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs b/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs deleted file mode 100644 index 0b06bec0e9eb..000000000000 --- a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs +++ /dev/null @@ -1,52 +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.IO.Pipelines; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Testing; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests -{ - public class PipeOptionsTests - { - [Theory] - [InlineData(10, 10, 10)] - [InlineData(null, 0, 0)] - public void AdaptedInputPipeOptionsConfiguredCorrectly(long? maxRequestBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) - { - var serviceContext = new TestServiceContext(); - serviceContext.ServerOptions.Limits.MaxRequestBufferSize = maxRequestBufferSize; - - var connectionLifetime = new HttpConnection(new HttpConnectionContext - { - ServiceContext = serviceContext - }); - - Assert.Equal(expectedMaximumSizeLow, connectionLifetime.AdaptedInputPipeOptions.ResumeWriterThreshold); - Assert.Equal(expectedMaximumSizeHigh, connectionLifetime.AdaptedInputPipeOptions.PauseWriterThreshold); - Assert.Same(serviceContext.Scheduler, connectionLifetime.AdaptedInputPipeOptions.ReaderScheduler); - Assert.Same(PipeScheduler.Inline, connectionLifetime.AdaptedInputPipeOptions.WriterScheduler); - } - - [Theory] - [InlineData(10, 10, 10)] - [InlineData(null, 0, 0)] - public void AdaptedOutputPipeOptionsConfiguredCorrectly(long? maxRequestBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) - { - var serviceContext = new TestServiceContext(); - serviceContext.ServerOptions.Limits.MaxResponseBufferSize = maxRequestBufferSize; - - var connectionLifetime = new HttpConnection(new HttpConnectionContext - { - ServiceContext = serviceContext - }); - - Assert.Equal(expectedMaximumSizeLow, connectionLifetime.AdaptedOutputPipeOptions.ResumeWriterThreshold); - Assert.Equal(expectedMaximumSizeHigh, connectionLifetime.AdaptedOutputPipeOptions.PauseWriterThreshold); - Assert.Same(PipeScheduler.Inline, connectionLifetime.AdaptedOutputPipeOptions.ReaderScheduler); - Assert.Same(PipeScheduler.Inline, connectionLifetime.AdaptedOutputPipeOptions.WriterScheduler); - } - } -} diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index d18af3aa39bf..f2b415966728 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -185,7 +185,7 @@ public async Task OneToTenThreads(int threadCount) return context.Response.WriteAsync("Hello World"); }); - listenOptions.UseHttpServer(listenOptions.ConnectionAdapters, serviceContext, testApplication, HttpProtocols.Http1); + listenOptions.UseHttpServer(serviceContext, testApplication, HttpProtocols.Http1); var transportContext = new TestLibuvTransportContext { diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs index 794f011ecc7b..c6b37eb216b0 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -2,11 +2,10 @@ using System.IO; using System.Net; using System.Security.Authentication; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -39,7 +38,20 @@ public static void Main(string[] args) { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; listenOptions.UseHttps(); - listenOptions.ConnectionAdapters.Add(new TlsFilterAdapter()); + listenOptions.Use((context, next) => + { + // https://tools.ietf.org/html/rfc7540#appendix-A + // Allows filtering TLS handshakes on a per connection basis + + var tlsFeature = context.Features.Get(); + + if (tlsFeature.CipherAlgorithm == CipherAlgorithmType.Null) + { + throw new NotSupportedException("Prohibited cipher: " + tlsFeature.CipherAlgorithm); + } + + return next(); + }); }); // Prior knowledge, no TLS handshake. WARNING: Not supported by browsers @@ -54,38 +66,5 @@ public static void Main(string[] args) hostBuilder.Build().Run(); } - - // https://tools.ietf.org/html/rfc7540#appendix-A - // Allows filtering TLS handshakes on a per connection basis - private class TlsFilterAdapter : IConnectionAdapter - { - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) - { - var tlsFeature = context.Features.Get(); - - if (tlsFeature.CipherAlgorithm == CipherAlgorithmType.Null) - { - throw new NotSupportedException("Prohibited cipher: " + tlsFeature.CipherAlgorithm); - } - - return Task.FromResult(new AdaptedConnection(context.ConnectionStream)); - } - - private class AdaptedConnection : IAdaptedConnection - { - public AdaptedConnection(Stream adaptedStream) - { - ConnectionStream = adaptedStream; - } - - public Stream ConnectionStream { get; } - - public void Dispose() - { - } - } - } } } diff --git a/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs b/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs index 9f81ceec5fd0..a5951b6d6c72 100644 --- a/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs +++ b/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs @@ -2,168 +2,96 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; +using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Connections; namespace Microsoft.AspNetCore.Testing { - public class PassThroughConnectionAdapter : IConnectionAdapter + public class PassThroughConnectionMiddleware { - public bool IsHttps => false; + private readonly ConnectionDelegate _next; - public Task OnConnectionAsync(ConnectionAdapterContext context) + public PassThroughConnectionMiddleware(ConnectionDelegate next) { - var adapted = new AdaptedConnection(new PassThroughStream(context.ConnectionStream)); - return Task.FromResult(adapted); + _next = next; } - private class AdaptedConnection : IAdaptedConnection + public Task OnConnectionAsync(ConnectionContext context) { - public AdaptedConnection(Stream stream) - { - ConnectionStream = stream; - } - - public Stream ConnectionStream { get; } - - public void Dispose() - { - } + context.Transport = new PassThroughDuplexPipe(context.Transport); + return _next(context); } - private class PassThroughStream : Stream + private class PassThroughDuplexPipe : IDuplexPipe { - private readonly Stream _innerStream; - - public PassThroughStream(Stream innerStream) + public PassThroughDuplexPipe(IDuplexPipe duplexPipe) { - _innerStream = innerStream; + Input = new PassThroughPipeReader(duplexPipe.Input); + Output = new PassThroughPipeWriter(duplexPipe.Output); } - public override bool CanRead => _innerStream.CanRead; - - public override bool CanSeek => _innerStream.CanSeek; + public PipeReader Input { get; } - public override bool CanTimeout => _innerStream.CanTimeout; + public PipeWriter Output { get; } - public override bool CanWrite => _innerStream.CanWrite; - - public override long Length => _innerStream.Length; - - public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; } - - public override int ReadTimeout { get => _innerStream.ReadTimeout; set => _innerStream.ReadTimeout = value; } - - public override int WriteTimeout { get => _innerStream.WriteTimeout; set => _innerStream.WriteTimeout = value; } - - public override int Read(byte[] buffer, int offset, int count) + private class PassThroughPipeWriter : PipeWriter { - return _innerStream.Read(buffer, offset, count); - } + private PipeWriter _output; - public override int ReadByte() - { - return _innerStream.ReadByte(); - } + public PassThroughPipeWriter(PipeWriter output) + { + _output = output; + } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _innerStream.ReadAsync(buffer, offset, count, cancellationToken); - } + public override void Advance(int bytes) => _output.Advance(bytes); - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _innerStream.BeginRead(buffer, offset, count, callback, state); - } + public override void CancelPendingFlush() => _output.CancelPendingFlush(); - public override int EndRead(IAsyncResult asyncResult) - { - return _innerStream.EndRead(asyncResult); - } + public override void Complete(Exception exception = null) => _output.Complete(exception); - public override void Write(byte[] buffer, int offset, int count) - { - _innerStream.Write(buffer, offset, count); - } + public override ValueTask FlushAsync(CancellationToken cancellationToken = default) => _output.FlushAsync(cancellationToken); + public override Memory GetMemory(int sizeHint = 0) => _output.GetMemory(sizeHint); - public override void WriteByte(byte value) - { - _innerStream.WriteByte(value); - } + public override Span GetSpan(int sizeHint = 0) => _output.GetSpan(sizeHint); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _innerStream.WriteAsync(buffer, offset, count, cancellationToken); + public override void OnReaderCompleted(Action callback, object state) => _output.OnReaderCompleted(callback, state); } - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + private class PassThroughPipeReader : PipeReader { - return _innerStream.BeginWrite(buffer, offset, count, callback, state); - } + private PipeReader _input; - public override void EndWrite(IAsyncResult asyncResult) - { - _innerStream.EndWrite(asyncResult); - } + public PassThroughPipeReader(PipeReader input) + { + _input = input; + } - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken); - } + public override void AdvanceTo(SequencePosition consumed) => _input.AdvanceTo(consumed); - public override void Flush() - { - _innerStream.Flush(); - } + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) => _input.AdvanceTo(consumed, examined); - public override Task FlushAsync(CancellationToken cancellationToken) - { - return _innerStream.FlushAsync(); + public override void CancelPendingRead() => _input.CancelPendingRead(); - } + public override void Complete(Exception exception = null) => _input.Complete(exception); - public override long Seek(long offset, SeekOrigin origin) - { - return _innerStream.Seek(offset, origin); - } + public override void OnWriterCompleted(Action callback, object state) => _input.OnWriterCompleted(callback, state); - public override void SetLength(long value) - { - _innerStream.SetLength(value); - } + public override ValueTask ReadAsync(CancellationToken cancellationToken = default) => _input.ReadAsync(cancellationToken); - public override void Close() - { - _innerStream.Close(); - } - - public override int Read(Span buffer) - { - return _innerStream.Read(buffer); - } - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - return _innerStream.ReadAsync(buffer, cancellationToken); - } - - public override void Write(ReadOnlySpan buffer) - { - _innerStream.Write(buffer); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - return _innerStream.WriteAsync(buffer, cancellationToken); + public override bool TryRead(out ReadResult result) => _input.TryRead(out result); } + } + } - public override void CopyTo(Stream destination, int bufferSize) - { - _innerStream.CopyTo(destination, bufferSize); - } + public static class PassThroughConnectionMiddlewareExtensions + { + public static TBuilder UsePassThrough(this TBuilder builder) where TBuilder : IConnectionBuilder + { + builder.Use(next => new PassThroughConnectionMiddleware(next).OnConnectionAsync); + return builder; } } } diff --git a/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs index 9e0f462a831d..eb2e9d493013 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs @@ -6,7 +6,6 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Testing; using Xunit; @@ -18,12 +17,10 @@ public class ConnectionAdapterTests : LoggedTest [Fact] public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new ThrowingConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => context => throw new Exception()); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions)) { @@ -47,15 +44,5 @@ await connection.Send( await server.StopAsync(); } } - - private class ThrowingConnectionAdapter : IConnectionAdapter - { - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) - { - throw new Exception(); - } - } } } diff --git a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs index 039b148db018..95403f42816b 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs @@ -297,7 +297,7 @@ private async Task StartWebHost(long? maxRequestBufferSize, { if (useConnectionAdapter) { - listenOptions.ConnectionAdapters.Add(new PassThroughConnectionAdapter()); + listenOptions.UsePassThrough(); } }); diff --git a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs index 8e1555aa2e75..cac39502328d 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -40,10 +40,7 @@ public class RequestTests : LoggedTest public static TheoryData ConnectionAdapterData => new TheoryData { new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - } + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)).UsePassThrough() }; [Theory] @@ -509,7 +506,7 @@ public async Task AbortingTheConnectionSendsFIN() [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedTokenFiresOnClientFIN(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var appStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -546,7 +543,7 @@ await connection.Send( [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedTokenFiresOnServerFIN(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(context => @@ -583,7 +580,7 @@ await connection.ReceiveEnd($"HTTP/1.1 200 OK", [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedTokenFiresOnServerAbort(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(context => @@ -628,7 +625,7 @@ public async Task RequestsCanBeAbortedMidRead(ListenOptions listenOptions) // This needs a timeout. const int applicationAbortedConnectionId = 34; - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var readTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var registrationTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -746,6 +743,7 @@ public async Task ServerCanAbortConnectionAfterUnobservedClose(ListenOptions lis var mockKestrelTrace = new Mock(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { + ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count, ServerOptions = { Limits = @@ -805,7 +803,7 @@ public async Task AppCanHandleClientAbortingConnectionMidRequest(ListenOptions l var appStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock(); - var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object); + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var scratchBuffer = new byte[4096]; diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index 5604f402297f..3958bcd97c4b 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -36,10 +36,7 @@ public class ResponseTests : TestApplicationErrorLoggerLoggedTest public static TheoryData ConnectionAdapterData => new TheoryData { new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - } + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)).UsePassThrough() }; [Fact] @@ -160,7 +157,7 @@ public async Task WriteAfterConnectionCloseNoops(ListenOptions listenOptions) { appCompleted.TrySetException(ex); } - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }, listenOptions)) { using (var connection = server.CreateConnection()) { @@ -222,7 +219,7 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List } writeTcs.SetException(new Exception("This shouldn't be reached.")); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }, listenOptions)) { using (var connection = server.CreateConnection()) { @@ -276,6 +273,7 @@ public async Task WritingToConnectionAfterUnobservedCloseTriggersRequestAbortedT var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { + ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count, ServerOptions = { Limits = @@ -370,7 +368,7 @@ public async Task AppCanHandleClientAbortingConnectionMidResponse(ListenOptions await requestAborted.Task.DefaultTimeout(); appCompletedTcs.SetResult(null); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }, listenOptions)) { using (var connection = server.CreateConnection()) { @@ -424,7 +422,8 @@ public async Task ClientAbortingConnectionImmediatelyIsNotLoggedHigherThanDebug( // There's not guarantee that the app even gets invoked in this test. The connection reset can be observed // as early as accept. - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + var testServiceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; + using (var server = new TestServer(context => Task.CompletedTask, testServiceContext, listenOptions)) { for (var i = 0; i < numConnections; i++) { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs index 8b1387f88ba4..b7f5b02de227 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs @@ -7,10 +7,10 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Testing; @@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests { public class ConnectionAdapterTests : TestApplicationErrorLoggerLoggedTest { - public static TheoryData EchoAppRequestDelegates => new TheoryData { @@ -32,13 +31,16 @@ public class ConnectionAdapterTests : TestApplicationErrorLoggerLoggedTest [MemberData(nameof(EchoAppRequestDelegates))] public async Task CanReadAndWriteWithRewritingConnectionAdapter(RequestDelegate requestDelegate) { - var adapter = new RewritingConnectionAdapter(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + RewritingConnectionMiddleware middleware = null; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => { - ConnectionAdapters = { adapter } - }; + middleware = new RewritingConnectionMiddleware(next); + return middleware.OnConnectionAsync; + }); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?"; @@ -57,19 +59,17 @@ await connection.ReceiveEnd( } } - Assert.Equal(sendString.Length, adapter.BytesRead); + Assert.Equal(sendString.Length, middleware.BytesRead); } [Theory] [MemberData(nameof(EchoAppRequestDelegates))] public async Task CanReadAndWriteWithAsyncConnectionAdapter(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new AsyncConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => new AsyncConnectionMiddleware(next).OnConnectionAsync); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -94,12 +94,10 @@ await connection.ReceiveEnd( [MemberData(nameof(EchoAppRequestDelegates))] public async Task ImmediateFinAfterOnConnectionAsyncClosesGracefully(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new AsyncConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => new AsyncConnectionMiddleware(next).OnConnectionAsync); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -116,12 +114,10 @@ public async Task ImmediateFinAfterOnConnectionAsyncClosesGracefully(RequestDele [MemberData(nameof(EchoAppRequestDelegates))] public async Task ImmediateFinAfterThrowingClosesGracefully(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new ThrowingConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => context => throw new InvalidOperationException()); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -139,12 +135,10 @@ public async Task ImmediateFinAfterThrowingClosesGracefully(RequestDelegate requ [MemberData(nameof(EchoAppRequestDelegates))] public async Task ImmediateShutdownAfterOnConnectionAsyncDoesNotCrash(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new AsyncConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => new AsyncConnectionMiddleware(next).OnConnectionAsync); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; TestApplicationErrorLogger.ThrowOnUngracefulShutdown = false; @@ -167,13 +161,18 @@ public async Task ImmediateShutdownAfterOnConnectionAsyncDoesNotCrash(RequestDel [Fact] public async Task ImmediateShutdownDuringOnConnectionAsyncDoesNotCrash() { - var waitingConnectionAdapter = new WaitingConnectionAdapter(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => { - ConnectionAdapters = { waitingConnectionAdapter } - }; + return async context => + { + await tcs.Task; + await next(context); + }; + }); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions)) { @@ -181,13 +180,9 @@ public async Task ImmediateShutdownDuringOnConnectionAsyncDoesNotCrash() using (var connection = server.CreateConnection()) { - var closingMessageTask = TestApplicationErrorLogger.WaitForMessage(m => m.Message.Contains(CoreStrings.ServerShutdownDuringConnectionInitialization)); - stopTask = server.StopAsync(); - await closingMessageTask.DefaultTimeout(); - - waitingConnectionAdapter.Complete(); + tcs.TrySetResult(null); } await stopTask; @@ -198,12 +193,15 @@ public async Task ImmediateShutdownDuringOnConnectionAsyncDoesNotCrash() [MemberData(nameof(EchoAppRequestDelegates))] public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + var connectionId = ""; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => context => { - ConnectionAdapters = { new ThrowingConnectionAdapter() } - }; + connectionId = context.ConnectionId; + throw new InvalidOperationException(); + }); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -218,18 +216,16 @@ await connection.Send( } } - Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains($"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.")); + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Unhandled exception while processing " + connectionId + ".")); } [Fact] public async Task CanFlushAsyncWithConnectionAdapter() { var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - }; + .UsePassThrough(); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(async context => { @@ -258,11 +254,9 @@ await connection.ReceiveEnd( public async Task CanFlushAsyncWithConnectionAdapterPipeWriter() { var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - }; + .UsePassThrough(); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(async context => { @@ -287,71 +281,67 @@ await connection.ReceiveEnd( } } - private class RewritingConnectionAdapter : IConnectionAdapter + private class RewritingConnectionMiddleware { private RewritingStream _rewritingStream; + private readonly ConnectionDelegate _next; - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) + public RewritingConnectionMiddleware(ConnectionDelegate next) { - _rewritingStream = new RewritingStream(context.ConnectionStream); - return Task.FromResult(new AdaptedConnection(_rewritingStream)); + _next = next; } - public int BytesRead => _rewritingStream.BytesRead; - } - - private class AsyncConnectionAdapter : IConnectionAdapter - { - public bool IsHttps => false; - - public async Task OnConnectionAsync(ConnectionAdapterContext context) + public async Task OnConnectionAsync(ConnectionContext context) { - await Task.Yield(); - return new AdaptedConnection(new RewritingStream(context.ConnectionStream)); - } - } - - private class WaitingConnectionAdapter : IConnectionAdapter - { - private TaskCompletionSource _waitingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var old = context.Transport; + var duplexPipe = new DuplexPipeStreamAdapter(context.Transport, s => new RewritingStream(s)); + _rewritingStream = duplexPipe.Stream; - public bool IsHttps => false; - - public async Task OnConnectionAsync(ConnectionAdapterContext context) - { - await _waitingTcs.Task; - return new AdaptedConnection(context.ConnectionStream); + try + { + await using (duplexPipe) + { + context.Transport = duplexPipe; + await _next(context); + } + } + finally + { + context.Transport = old; + } } - public void Complete() - { - _waitingTcs.TrySetResult(null); - } + public int BytesRead => _rewritingStream.BytesRead; } - private class ThrowingConnectionAdapter : IConnectionAdapter + private class AsyncConnectionMiddleware { - public bool IsHttps => false; + private readonly ConnectionDelegate _next; - public Task OnConnectionAsync(ConnectionAdapterContext context) + public AsyncConnectionMiddleware(ConnectionDelegate next) { - throw new Exception(); + _next = next; } - } - private class AdaptedConnection : IAdaptedConnection - { - public AdaptedConnection(Stream adaptedStream) + public async Task OnConnectionAsync(ConnectionContext context) { - ConnectionStream = adaptedStream; - } + await Task.Yield(); - public Stream ConnectionStream { get; } + var old = context.Transport; + var duplexPipe = new DuplexPipeStreamAdapter(context.Transport, s => new RewritingStream(s)); - public void Dispose() - { + try + { + await using (duplexPipe) + { + context.Transport = duplexPipe; + await _next(context); + } + } + finally + { + context.Transport = old; + } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs index 75d04b3285ce..a1646a8fbdd5 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs @@ -6,7 +6,7 @@ using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2 { @@ -34,7 +34,7 @@ private static async Task CopyToAsync(Stream stream, Pipe pipe, CancellationToke { try { - await stream.CopyToAsync(new RawStream(null, pipe.Writer), bufferSize: 4096, cancellationToken); + await stream.CopyToAsync(new DuplexPipeStream(null, pipe.Writer), bufferSize: 4096, cancellationToken); } catch (OperationCanceledException) { @@ -49,4 +49,4 @@ private static async Task CopyToAsync(Stream stream, Pipe pipe, CancellationToke } } } -} \ No newline at end of file +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs index c7ae1d3d5d0e..42e98c878ab3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs @@ -23,7 +23,7 @@ public async Task LoggingConnectionAdapterCanBeAddedBeforeAndAfterHttpsAdapter() context.Response.ContentLength = 12; return context.Response.WriteAsync("Hello World!"); }, - new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1}, + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 3 }, listenOptions => { listenOptions.UseConnectionLogging(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs index f0603fef3a15..f01dd716338d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs @@ -18,17 +18,14 @@ public class ResponseDrainingTests : TestApplicationErrorLoggerLoggedTest public static TheoryData ConnectionAdapterData => new TheoryData { new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - } + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)).UsePassThrough() }; [Theory] [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var minRate = new MinDataRate(16384, TimeSpan.FromSeconds(2)); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs index f698a4908d02..b8e3209f8887 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTrans internal class InMemoryConnection : StreamBackedTestConnection { public InMemoryConnection(InMemoryTransportConnection transportConnection) - : base(new RawStream(transportConnection.Output, transportConnection.Input)) + : base(new DuplexPipeStream(transportConnection.Output, transportConnection.Input)) { TransportConnection = transportConnection; } From 559a266d26143f3603c4ca970b95f6c9caf38687 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 20 Jun 2019 13:47:57 +0200 Subject: [PATCH 2/3] Leave the underlying Stream open --- .../Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs | 7 ++++--- .../src/Middleware/Internal/DuplexPipeStreamAdapter.cs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs index 41b06dff3735..d1046acf86e7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs @@ -85,19 +85,20 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) context.Features.Set(feature); context.Features.Set(feature); - // TODO: Handle the cases where this can be null var memoryPoolFeature = context.Features.Get(); var inputPipeOptions = new StreamPipeReaderOptions ( pool: memoryPoolFeature.MemoryPool, bufferSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize(), - minimumReadSize: memoryPoolFeature.MemoryPool.GetMinimumAllocSize() + minimumReadSize: memoryPoolFeature.MemoryPool.GetMinimumAllocSize(), + leaveOpen: true ); var outputPipeOptions = new StreamPipeWriterOptions ( - pool: memoryPoolFeature.MemoryPool + pool: memoryPoolFeature.MemoryPool, + leaveOpen: true ); SslDuplexPipe sslDuplexPipe = null; diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs index b387af0f7f63..06e2eafbe9a6 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal internal class DuplexPipeStreamAdapter : DuplexPipeStream, IDuplexPipe where TStream : Stream { public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func createStream) : - this(duplexPipe, new StreamPipeReaderOptions(), new StreamPipeWriterOptions(), createStream) + this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), createStream) { } From 8e783c06e787ef6f080ea6c7cb7c6221c1034cd6 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 20 Jun 2019 21:49:59 +0200 Subject: [PATCH 3/3] PR feedback and cleanup --- .../Core/src/HttpsConnectionAdapterOptions.cs | 7 --- .../src/Internal/HttpsConnectionMiddleware.cs | 46 +++++++++---------- .../Core/src/ListenOptionsHttpsExtensions.cs | 5 +- .../Internal/DuplexPipeStreamAdapter.cs | 4 +- .../Internal/LoggingConnectionMiddleware.cs | 4 +- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs index 99084fdc5263..baf7785773ce 100644 --- a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs +++ b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.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; -using System.IO.Pipelines; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; @@ -97,11 +96,5 @@ public TimeSpan HandshakeTimeout _handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue; } } - - internal PipeScheduler Scheduler { get; set; } = PipeScheduler.ThreadPool; - - internal long? MaxInputBufferSize { get; set; } - - internal long? MaxOutputBufferSize { get; set; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs index d1046acf86e7..2449fc514629 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs @@ -6,7 +6,6 @@ using System.IO; using System.IO.Pipelines; using System.Net.Security; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; @@ -26,7 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal internal class HttpsConnectionMiddleware { private readonly ConnectionDelegate _next; - private readonly HttpsConnectionAdapterOptions _options; private readonly ILogger _logger; private readonly X509Certificate2 _serverCertificate; @@ -85,19 +83,19 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) context.Features.Set(feature); context.Features.Set(feature); - var memoryPoolFeature = context.Features.Get(); + var memoryPool = context.Features.Get()?.MemoryPool; var inputPipeOptions = new StreamPipeReaderOptions ( - pool: memoryPoolFeature.MemoryPool, - bufferSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize(), - minimumReadSize: memoryPoolFeature.MemoryPool.GetMinimumAllocSize(), + pool: memoryPool, + bufferSize: memoryPool.GetMinimumSegmentSize(), + minimumReadSize: memoryPool.GetMinimumAllocSize(), leaveOpen: true ); var outputPipeOptions = new StreamPipeWriterOptions ( - pool: memoryPoolFeature.MemoryPool, + pool: memoryPool, leaveOpen: true ); @@ -147,6 +145,8 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) certificateRequired = true; } + var sslStream = sslDuplexPipe.Stream; + using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { @@ -158,7 +158,7 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) { selector = (sender, name) => { - context.Features.Set(sslDuplexPipe.Stream); + context.Features.Set(sslStream); var cert = _serverCertificateSelector(context, name); if (cert != null) { @@ -193,41 +193,41 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) _options.OnAuthenticate?.Invoke(context, sslOptions); - await sslDuplexPipe.Stream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); + await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); } catch (OperationCanceledException) { _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); - await sslDuplexPipe.Stream.DisposeAsync(); + await sslStream.DisposeAsync(); return; } catch (Exception ex) when (ex is IOException || ex is AuthenticationException) { _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); - await sslDuplexPipe.Stream.DisposeAsync(); + await sslStream.DisposeAsync(); return; } } - feature.ApplicationProtocol = sslDuplexPipe.Stream.NegotiatedApplicationProtocol.Protocol; + feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set(feature); - feature.ClientCertificate = ConvertToX509Certificate2(sslDuplexPipe.Stream.RemoteCertificate); - feature.CipherAlgorithm = sslDuplexPipe.Stream.CipherAlgorithm; - feature.CipherStrength = sslDuplexPipe.Stream.CipherStrength; - feature.HashAlgorithm = sslDuplexPipe.Stream.HashAlgorithm; - feature.HashStrength = sslDuplexPipe.Stream.HashStrength; - feature.KeyExchangeAlgorithm = sslDuplexPipe.Stream.KeyExchangeAlgorithm; - feature.KeyExchangeStrength = sslDuplexPipe.Stream.KeyExchangeStrength; - feature.Protocol = sslDuplexPipe.Stream.SslProtocol; + feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); + feature.CipherAlgorithm = sslStream.CipherAlgorithm; + feature.CipherStrength = sslStream.CipherStrength; + feature.HashAlgorithm = sslStream.HashAlgorithm; + feature.HashStrength = sslStream.HashStrength; + feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; + feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; + feature.Protocol = sslStream.SslProtocol; - var original = context.Transport; + var originalTransport = context.Transport; try { context.Transport = sslDuplexPipe; // Disposing the stream will dispose the sslDuplexPipe - await using (sslDuplexPipe.Stream) + await using (sslStream) { await _next(context); } @@ -235,7 +235,7 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) finally { // Restore the original so that it gets closed appropriately - context.Transport = original; + context.Transport = originalTransport; } } diff --git a/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs index fd119bd7851b..e3d411e08772 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs @@ -26,7 +26,7 @@ public static class ListenOptionsHttpsExtensions /// The to configure. /// The . public static ListenOptions UseHttps(this ListenOptions listenOptions) => listenOptions.UseHttps(_ => { }); - + /// /// Configure Kestrel to use HTTPS. /// @@ -218,9 +218,6 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsConn // Set the list of protocols from listen options httpsOptions.HttpProtocols = listenOptions.Protocols; - httpsOptions.MaxInputBufferSize = listenOptions.KestrelServerOptions?.Limits.MaxRequestBufferSize; - httpsOptions.MaxOutputBufferSize = listenOptions.KestrelServerOptions?.Limits.MaxResponseBufferSize; - listenOptions.IsTls = true; listenOptions.Use(next => diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs index 06e2eafbe9a6..5b5cc019057c 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs @@ -1,3 +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.IO; using System.IO.Pipelines; @@ -14,7 +17,6 @@ internal class DuplexPipeStreamAdapter : DuplexPipeStream, IDuplexPipe public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func createStream) : this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), createStream) { - } public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func createStream) : base(duplexPipe.Input, duplexPipe.Output) diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs index 0df816b827d6..ed9ffb819c7b 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs @@ -22,7 +22,7 @@ public LoggingConnectionMiddleware(ConnectionDelegate next, ILogger logger) public async Task OnConnectionAsync(ConnectionContext context) { - var oldTranspot = context.Transport; + var oldTransport = context.Transport; try { @@ -35,7 +35,7 @@ public async Task OnConnectionAsync(ConnectionContext context) } finally { - context.Transport = oldTranspot; + context.Transport = oldTransport; } }