From 5c775073a4abb5d241f1a1b9587d3d23a6fbe14f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 16 Aug 2017 00:02:48 -0700 Subject: [PATCH] Initial bedrock refactoring (#1995) - Added Protocols.Abstractions - IConnectionHandler.OnConnection takes an IFeatureCollection instead of IConnectionInfo - Removed IConnectionContext and IConnectionInformation replaced with IConnectionTransportFeature - Updated FrameConnectionContext and FrameContext to have the relevant state instead of flowing the ConnectionInformation. - Updated tests --- KestrelHttpServer.sln | 17 +- src/Kestrel.Core/Internal/AddressBinder.cs | 2 +- .../Internal/ConnectionHandler.cs | 63 ++++- src/Kestrel.Core/Internal/FrameConnection.cs | 17 +- .../Internal/FrameConnectionContext.cs | 6 +- src/Kestrel.Core/Internal/Http/Frame.cs | 9 +- .../Internal/Http/FrameContext.cs | 7 +- src/Kestrel.Core/Internal/Http/FrameOfT.cs | 2 +- .../Internal/Http/FrameRequestStream.cs | 1 + ...rameConnectionManagerShutdownExtensions.cs | 1 + .../Infrastructure/KestrelEventSource.cs | 13 +- .../Internal/RejectionConnection.cs | 13 +- .../Internal/IConnectionContext.cs | 19 -- .../Internal/IConnectionHandler.cs | 4 +- .../Internal/IConnectionInformation.cs | 19 -- .../Internal/TransportConnection.Features.cs | 215 ++++++++++++++++++ .../Internal/TransportConnection.cs | 33 +++ .../Kestrel.Transport.Abstractions.csproj | 10 +- .../Internal/LibuvConnection.cs | 39 ++-- .../Internal/LibuvConnectionContext.cs | 17 +- src/Kestrel.Transport.Libuv/LibuvTransport.cs | 1 + .../SocketConnection.cs | 39 ++-- .../SocketTransport.cs | 1 + .../ConnectionContext.cs | 17 ++ .../ConnectionDelegate.cs | 9 + .../DefaultConnectionContext.cs | 48 ++++ .../Exceptions/AddressInUseException.cs | 2 +- .../Exceptions/ConnectionAbortedException.cs | 2 +- .../Exceptions/ConnectionResetException.cs | 2 +- .../Features/IConnectionApplicationFeature.cs | 18 ++ .../Features/IConnectionIdFeature.cs | 11 + .../Features/IConnectionTransportFeature.cs | 18 ++ .../IConnectionBuilder.cs | 15 ++ src/Protocols.Abstractions/PipeConnection.cs | 23 ++ .../Protocols.Abstractions.csproj | 30 +++ test/Kestrel.Core.Tests/AddressBinderTests.cs | 1 + .../FrameConnectionTests.cs | 5 +- .../FrameResponseHeadersTests.cs | 5 +- test/Kestrel.Core.Tests/FrameTests.cs | 5 +- test/Kestrel.Core.Tests/TestInput.cs | 5 +- test/Kestrel.FunctionalTests/RequestTests.cs | 1 + .../FrameFeatureCollection.cs | 7 +- .../FrameParsingOverheadBenchmark.cs | 5 +- .../FrameWritingBenchmark.cs | 5 +- .../Mocks/MockConnectionInformation.cs | 20 -- .../RequestParsingBenchmark.cs | 5 +- .../ResponseHeaderCollectionBenchmark.cs | 5 +- .../ResponseHeadersWritingBenchmark.cs | 5 +- .../LibuvOutputConsumerTests.cs | 5 +- .../TestHelpers/MockConnectionHandler.cs | 25 +- test/shared/MockConnectionInformation.cs | 22 -- 51 files changed, 632 insertions(+), 237 deletions(-) delete mode 100644 src/Kestrel.Transport.Abstractions/Internal/IConnectionContext.cs delete mode 100644 src/Kestrel.Transport.Abstractions/Internal/IConnectionInformation.cs create mode 100644 src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs create mode 100644 src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs create mode 100644 src/Protocols.Abstractions/ConnectionContext.cs create mode 100644 src/Protocols.Abstractions/ConnectionDelegate.cs create mode 100644 src/Protocols.Abstractions/DefaultConnectionContext.cs rename src/{Kestrel.Transport.Abstractions => Protocols.Abstractions}/Exceptions/AddressInUseException.cs (85%) rename src/{Kestrel.Transport.Abstractions => Protocols.Abstractions}/Exceptions/ConnectionAbortedException.cs (84%) rename src/{Kestrel.Transport.Abstractions => Protocols.Abstractions}/Exceptions/ConnectionResetException.cs (86%) create mode 100644 src/Protocols.Abstractions/Features/IConnectionApplicationFeature.cs create mode 100644 src/Protocols.Abstractions/Features/IConnectionIdFeature.cs create mode 100644 src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs create mode 100644 src/Protocols.Abstractions/IConnectionBuilder.cs create mode 100644 src/Protocols.Abstractions/PipeConnection.cs create mode 100644 src/Protocols.Abstractions/Protocols.Abstractions.csproj delete mode 100644 test/Kestrel.Performance/Mocks/MockConnectionInformation.cs delete mode 100644 test/shared/MockConnectionInformation.cs diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index 4c9791d022..60221f61ae 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26706.0 +VisualStudioVersion = 15.0.26730.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}" ProjectSection(SolutionItems) = preProject @@ -80,6 +80,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Tests", "test\Kestrel.Tests\Kestrel.Tests.csproj", "{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protocols.Abstractions", "src\Protocols.Abstractions\Protocols.Abstractions.csproj", "{6956CF5C-3163-4398-8628-4ECA569245B5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -258,6 +260,18 @@ Global {4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}.Release|x64.Build.0 = Release|Any CPU {4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}.Release|x86.ActiveCfg = Release|Any CPU {4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}.Release|x86.Build.0 = Release|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Debug|x64.ActiveCfg = Debug|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Debug|x64.Build.0 = Debug|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Debug|x86.ActiveCfg = Debug|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Debug|x86.Build.0 = Debug|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Release|Any CPU.Build.0 = Release|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Release|x64.ActiveCfg = Release|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Release|x64.Build.0 = Release|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Release|x86.ActiveCfg = Release|Any CPU + {6956CF5C-3163-4398-8628-4ECA569245B5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -279,6 +293,7 @@ Global {2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} {D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} {4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} + {6956CF5C-3163-4398-8628-4ECA569245B5} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071} diff --git a/src/Kestrel.Core/Internal/AddressBinder.cs b/src/Kestrel.Core/Internal/AddressBinder.cs index 43f0c82abf..e4d1d18b93 100644 --- a/src/Kestrel.Core/Internal/AddressBinder.cs +++ b/src/Kestrel.Core/Internal/AddressBinder.cs @@ -9,8 +9,8 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal diff --git a/src/Kestrel.Core/Internal/ConnectionHandler.cs b/src/Kestrel.Core/Internal/ConnectionHandler.cs index 1d46a91f64..0dc7fbcbf0 100644 --- a/src/Kestrel.Core/Internal/ConnectionHandler.cs +++ b/src/Kestrel.Core/Internal/ConnectionHandler.cs @@ -2,8 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO.Pipelines; +using System.Net; using System.Threading; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols; +using Microsoft.AspNetCore.Protocols.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; @@ -24,38 +28,73 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal _application = application; } - public IConnectionContext OnConnection(IConnectionInformation connectionInfo) + public void OnConnection(IFeatureCollection features) { - var inputPipe = connectionInfo.PipeFactory.Create(GetInputPipeOptions(connectionInfo.InputWriterScheduler)); - var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(connectionInfo.OutputReaderScheduler)); + var connectionContext = new DefaultConnectionContext(features); + + var transportFeature = connectionContext.Features.Get(); + + var inputPipe = transportFeature.PipeFactory.Create(GetInputPipeOptions(transportFeature.InputWriterScheduler)); + var outputPipe = transportFeature.PipeFactory.Create(GetOutputPipeOptions(transportFeature.OutputReaderScheduler)); var connectionId = CorrelationIdGenerator.GetNextId(); var frameConnectionId = Interlocked.Increment(ref _lastFrameConnectionId); + // Set the transport and connection id + connectionContext.ConnectionId = connectionId; + transportFeature.Connection = new PipeConnection(inputPipe.Reader, outputPipe.Writer); + var applicationConnection = new PipeConnection(outputPipe.Reader, inputPipe.Writer); + if (!_serviceContext.ConnectionManager.NormalConnectionCount.TryLockOne()) { - var goAway = new RejectionConnection(inputPipe, outputPipe, connectionId, _serviceContext); + var goAway = new RejectionConnection(inputPipe, outputPipe, connectionId, _serviceContext) + { + Connection = applicationConnection + }; + + connectionContext.Features.Set(goAway); + goAway.Reject(); - return goAway; + return; } - var connection = new FrameConnection(new FrameConnectionContext + var frameConnectionContext = new FrameConnectionContext { ConnectionId = connectionId, FrameConnectionId = frameConnectionId, ServiceContext = _serviceContext, - ConnectionInformation = connectionInfo, + PipeFactory = connectionContext.PipeFactory, ConnectionAdapters = _listenOptions.ConnectionAdapters, Input = inputPipe, - Output = outputPipe, - }); + Output = outputPipe + }; + + var connectionFeature = connectionContext.Features.Get(); + + if (connectionFeature != null) + { + if (connectionFeature.LocalIpAddress != null) + { + frameConnectionContext.LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); + } + + if (connectionFeature.RemoteIpAddress != null) + { + frameConnectionContext.RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); + } + } + + var connection = new FrameConnection(frameConnectionContext) + { + Connection = applicationConnection + }; + + connectionContext.Features.Set(connection); // Since data cannot be added to the inputPipe by the transport until OnConnection returns, // Frame.ProcessRequestsAsync is guaranteed to unblock the transport thread before calling // application code. - connection.StartRequestProcessing(_application); - - return connection; + connection.StartRequestProcessing(_application); } // Internal for testing diff --git a/src/Kestrel.Core/Internal/FrameConnection.cs b/src/Kestrel.Core/Internal/FrameConnection.cs index ef3c6e15b4..dbe08fce25 100644 --- a/src/Kestrel.Core/Internal/FrameConnection.cs +++ b/src/Kestrel.Core/Internal/FrameConnection.cs @@ -6,19 +6,20 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Pipelines; +using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - public class FrameConnection : IConnectionContext, ITimeoutControl + public class FrameConnection : IConnectionApplicationFeature, ITimeoutControl { private readonly FrameConnectionContext _context; private List _adaptedConnections; @@ -55,8 +56,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public string ConnectionId => _context.ConnectionId; public IPipeWriter Input => _context.Input.Writer; public IPipeReader Output => _context.Output.Reader; + public IPEndPoint LocalEndPoint => _context.LocalEndPoint; + public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint; - private PipeFactory PipeFactory => _context.ConnectionInformation.PipeFactory; + private PipeFactory PipeFactory => _context.PipeFactory; // Internal for testing internal PipeOptions AdaptedInputPipeOptions => new PipeOptions @@ -77,6 +80,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal private IKestrelTrace Log => _context.ServiceContext.Log; + public IPipeConnection Connection { get; set; } + public void StartRequestProcessing(IHttpApplication application) { _lifetimeTask = ProcessRequestsAsync(application); @@ -89,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal try { Log.ConnectionStart(ConnectionId); - KestrelEventSource.Log.ConnectionStart(this, _context.ConnectionInformation); + KestrelEventSource.Log.ConnectionStart(this); AdaptedPipeline adaptedPipeline = null; var adaptedPipelineTask = Task.CompletedTask; @@ -156,7 +161,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal _frame = new Frame(application, new FrameContext { ConnectionId = _context.ConnectionId, - ConnectionInformation = _context.ConnectionInformation, + PipeFactory = PipeFactory, + LocalEndPoint = LocalEndPoint, + RemoteEndPoint = RemoteEndPoint, ServiceContext = _context.ServiceContext, TimeoutControl = this, Input = input, diff --git a/src/Kestrel.Core/Internal/FrameConnectionContext.cs b/src/Kestrel.Core/Internal/FrameConnectionContext.cs index 963d353021..57b08141f3 100644 --- a/src/Kestrel.Core/Internal/FrameConnectionContext.cs +++ b/src/Kestrel.Core/Internal/FrameConnectionContext.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.IO.Pipelines; +using System.Net; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { @@ -14,7 +14,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public long FrameConnectionId { get; set; } public ServiceContext ServiceContext { get; set; } public List ConnectionAdapters { get; set; } - public IConnectionInformation ConnectionInformation { get; set; } + public PipeFactory PipeFactory { get; set; } + public IPEndPoint LocalEndPoint { get; set; } + public IPEndPoint RemoteEndPoint { get; set; } public IPipe Input { get; set; } public IPipe Output { get; set; } diff --git a/src/Kestrel.Core/Internal/Http/Frame.cs b/src/Kestrel.Core/Internal/Http/Frame.cs index 8c6f648be3..251e256d41 100644 --- a/src/Kestrel.Core/Internal/Http/Frame.cs +++ b/src/Kestrel.Core/Internal/Http/Frame.cs @@ -15,8 +15,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -103,7 +103,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public IPipe RequestBodyPipe { get; } public ServiceContext ServiceContext => _frameContext.ServiceContext; - public IConnectionInformation ConnectionInformation => _frameContext.ConnectionInformation; + private IPEndPoint LocalEndPoint => _frameContext.LocalEndPoint; + private IPEndPoint RemoteEndPoint => _frameContext.RemoteEndPoint; public IFeatureCollection ConnectionFeatures { get; set; } public IPipeReader Input => _frameContext.Input; @@ -114,8 +115,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager; // Hold direct reference to ServerOptions since this is used very often in the request processing path private KestrelServerOptions ServerOptions { get; } - private IPEndPoint LocalEndPoint => ConnectionInformation.LocalEndPoint; - private IPEndPoint RemoteEndPoint => ConnectionInformation.RemoteEndPoint; protected string ConnectionId => _frameContext.ConnectionId; public string ConnectionIdFeature { get; set; } @@ -1376,7 +1375,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } private IPipe CreateRequestBodyPipe() - => ConnectionInformation.PipeFactory.Create(new PipeOptions + => _frameContext.PipeFactory.Create(new PipeOptions { ReaderScheduler = ServiceContext.ThreadPool, WriterScheduler = InlineScheduler.Default, diff --git a/src/Kestrel.Core/Internal/Http/FrameContext.cs b/src/Kestrel.Core/Internal/Http/FrameContext.cs index ac5d4de636..67274af488 100644 --- a/src/Kestrel.Core/Internal/Http/FrameContext.cs +++ b/src/Kestrel.Core/Internal/Http/FrameContext.cs @@ -2,8 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO.Pipelines; +using System.Net; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { @@ -11,7 +12,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { public string ConnectionId { get; set; } public ServiceContext ServiceContext { get; set; } - public IConnectionInformation ConnectionInformation { get; set; } + public PipeFactory PipeFactory { get; set; } + public IPEndPoint RemoteEndPoint { get; set; } + public IPEndPoint LocalEndPoint { get; set; } public ITimeoutControl TimeoutControl { get; set; } public IPipeReader Input { get; set; } public IPipe Output { get; set; } diff --git a/src/Kestrel.Core/Internal/Http/FrameOfT.cs b/src/Kestrel.Core/Internal/Http/FrameOfT.cs index 9e83e5351b..79ac93384a 100644 --- a/src/Kestrel.Core/Internal/Http/FrameOfT.cs +++ b/src/Kestrel.Core/Internal/Http/FrameOfT.cs @@ -6,8 +6,8 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http diff --git a/src/Kestrel.Core/Internal/Http/FrameRequestStream.cs b/src/Kestrel.Core/Internal/Http/FrameRequestStream.cs index aa2e9de5a7..5adc0a6e71 100644 --- a/src/Kestrel.Core/Internal/Http/FrameRequestStream.cs +++ b/src/Kestrel.Core/Internal/Http/FrameRequestStream.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { diff --git a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs b/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs index 3adaa4ae84..1a0df064db 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure diff --git a/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs b/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs index 628de18f67..4191141bbb 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics.Tracing; +using System.Net; using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; @@ -26,15 +29,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure // - Avoid renaming methods or parameters marked with EventAttribute. EventSource uses these to form the event object. [NonEvent] - public void ConnectionStart(IConnectionContext context, IConnectionInformation information) + public void ConnectionStart(FrameConnection connection) { // avoid allocating strings unless this event source is enabled if (IsEnabled()) { ConnectionStart( - context.ConnectionId, - information.LocalEndPoint.ToString(), - information.RemoteEndPoint.ToString()); + connection.ConnectionId, + connection.LocalEndPoint.ToString(), + connection.RemoteEndPoint.ToString()); } } @@ -53,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } [NonEvent] - public void ConnectionStop(IConnectionContext connection) + public void ConnectionStop(FrameConnection connection) { if (IsEnabled()) { diff --git a/src/Kestrel.Core/Internal/RejectionConnection.cs b/src/Kestrel.Core/Internal/RejectionConnection.cs index b14985c499..776a637258 100644 --- a/src/Kestrel.Core/Internal/RejectionConnection.cs +++ b/src/Kestrel.Core/Internal/RejectionConnection.cs @@ -3,12 +3,12 @@ using System; using System.IO.Pipelines; +using Microsoft.AspNetCore.Protocols.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - public class RejectionConnection : IConnectionContext + public class RejectionConnection : IConnectionApplicationFeature { private readonly IKestrelTrace _log; private readonly IPipe _input; @@ -26,6 +26,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public IPipeWriter Input => _input.Writer; public IPipeReader Output => _output.Reader; + public IPipeConnection Connection { get; set; } + public void Reject() { KestrelEventSource.Log.ConnectionRejected(ConnectionId); @@ -34,13 +36,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal _output.Writer.Complete(); } - // TODO: Remove these (https://github.com/aspnet/KestrelHttpServer/issues/1772) - void IConnectionContext.OnConnectionClosed(Exception ex) + void IConnectionApplicationFeature.OnConnectionClosed(Exception ex) { } - void IConnectionContext.Abort(Exception ex) + void IConnectionApplicationFeature.Abort(Exception ex) { } } -} +} \ No newline at end of file diff --git a/src/Kestrel.Transport.Abstractions/Internal/IConnectionContext.cs b/src/Kestrel.Transport.Abstractions/Internal/IConnectionContext.cs deleted file mode 100644 index 864037e68d..0000000000 --- a/src/Kestrel.Transport.Abstractions/Internal/IConnectionContext.cs +++ /dev/null @@ -1,19 +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.Pipelines; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface IConnectionContext - { - string ConnectionId { get; } - IPipeWriter Input { get; } - IPipeReader Output { get; } - - // TODO: Remove these (https://github.com/aspnet/KestrelHttpServer/issues/1772) - void OnConnectionClosed(Exception ex); - void Abort(Exception ex); - } -} diff --git a/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs b/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs index f3bf1c6ef3..9ae3f89c3f 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs @@ -1,10 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.AspNetCore.Http.Features; + namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { public interface IConnectionHandler { - IConnectionContext OnConnection(IConnectionInformation connectionInfo); + void OnConnection(IFeatureCollection features); } } diff --git a/src/Kestrel.Transport.Abstractions/Internal/IConnectionInformation.cs b/src/Kestrel.Transport.Abstractions/Internal/IConnectionInformation.cs deleted file mode 100644 index 4004824002..0000000000 --- a/src/Kestrel.Transport.Abstractions/Internal/IConnectionInformation.cs +++ /dev/null @@ -1,19 +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 System.Net; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal -{ - public interface IConnectionInformation - { - IPEndPoint RemoteEndPoint { get; } - IPEndPoint LocalEndPoint { get; } - - PipeFactory PipeFactory { get; } - - IScheduler InputWriterScheduler { get; } - IScheduler OutputReaderScheduler { get; } - } -} diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs new file mode 100644 index 0000000000..fd5d60050b --- /dev/null +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net; +using System.Text; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +{ + public partial class TransportConnection : IFeatureCollection, + IHttpConnectionFeature, + IConnectionIdFeature, + IConnectionTransportFeature + { + private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature); + private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); + private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); + private static readonly Type IConnectionApplicationFeatureType = typeof(IConnectionApplicationFeature); + + private object _currentIHttpConnectionFeature; + private object _currentIConnectionIdFeature; + private object _currentIConnectionTransportFeature; + private object _currentIConnectionApplicationFeature; + + private int _featureRevision; + + private List> MaybeExtra; + + private object ExtraFeatureGet(Type key) + { + if (MaybeExtra == null) + { + return null; + } + for (var i = 0; i < MaybeExtra.Count; i++) + { + var kv = MaybeExtra[i]; + if (kv.Key == key) + { + return kv.Value; + } + } + return null; + } + + private void ExtraFeatureSet(Type key, object value) + { + if (MaybeExtra == null) + { + MaybeExtra = new List>(2); + } + + for (var i = 0; i < MaybeExtra.Count; i++) + { + if (MaybeExtra[i].Key == key) + { + MaybeExtra[i] = new KeyValuePair(key, value); + return; + } + } + MaybeExtra.Add(new KeyValuePair(key, value)); + } + + bool IFeatureCollection.IsReadOnly => false; + + int IFeatureCollection.Revision => _featureRevision; + + string IHttpConnectionFeature.ConnectionId + { + get => ConnectionId; + set => ConnectionId = value; + } + + IPAddress IHttpConnectionFeature.RemoteIpAddress + { + get => RemoteAddress; + set => RemoteAddress = value; + } + + IPAddress IHttpConnectionFeature.LocalIpAddress + { + get => LocalAddress; + set => LocalAddress = value; + } + + int IHttpConnectionFeature.RemotePort + { + get => RemotePort; + set => RemotePort = value; + } + + int IHttpConnectionFeature.LocalPort + { + get => LocalPort; + set => LocalPort = value; + } + + PipeFactory IConnectionTransportFeature.PipeFactory => PipeFactory; + + IPipeConnection IConnectionTransportFeature.Connection + { + get => Transport; + set => Transport = value; + } + + object IFeatureCollection.this[Type key] + { + get => FastFeatureGet(key); + set => FastFeatureSet(key, value); + } + + TFeature IFeatureCollection.Get() + { + return (TFeature)FastFeatureGet(typeof(TFeature)); + } + + void IFeatureCollection.Set(TFeature instance) + { + FastFeatureSet(typeof(TFeature), instance); + } + + IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); + + private object FastFeatureGet(Type key) + { + if (key == IHttpConnectionFeatureType) + { + return _currentIHttpConnectionFeature; + } + + if (key == IConnectionIdFeatureType) + { + return _currentIConnectionIdFeature; + } + + if (key == IConnectionTransportFeatureType) + { + return _currentIConnectionTransportFeature; + } + + if (key == IConnectionApplicationFeatureType) + { + return _currentIConnectionApplicationFeature; + } + + return ExtraFeatureGet(key); + } + + private void FastFeatureSet(Type key, object feature) + { + _featureRevision++; + + if (key == IHttpConnectionFeatureType) + { + _currentIHttpConnectionFeature = feature; + return; + } + + if (key == IConnectionIdFeatureType) + { + _currentIConnectionIdFeature = feature; + return; + } + + if (key == IConnectionTransportFeatureType) + { + _currentIConnectionTransportFeature = feature; + return; + } + + if (key == IConnectionApplicationFeatureType) + { + _currentIConnectionApplicationFeature = feature; + return; + } + + ExtraFeatureSet(key, feature); + } + + private IEnumerable> FastEnumerable() + { + if (_currentIHttpConnectionFeature != null) + { + yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature); + } + + if (_currentIConnectionIdFeature != null) + { + yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); + } + + if (_currentIConnectionTransportFeature != null) + { + yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); + } + + if (_currentIConnectionApplicationFeature != null) + { + yield return new KeyValuePair(IConnectionApplicationFeatureType, _currentIConnectionApplicationFeature); + } + + if (MaybeExtra != null) + { + foreach (var item in MaybeExtra) + { + yield return item; + } + } + } + } +} diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs new file mode 100644 index 0000000000..a499dc8e6a --- /dev/null +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net; +using System.Text; +using Microsoft.AspNetCore.Protocols.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +{ + public abstract partial class TransportConnection + { + public TransportConnection() + { + _currentIConnectionIdFeature = this; + _currentIConnectionTransportFeature = this; + _currentIHttpConnectionFeature = this; + } + + public IPAddress RemoteAddress { get; set; } + public int RemotePort { get; set; } + public IPAddress LocalAddress { get; set; } + public int LocalPort { get; set; } + + public string ConnectionId { get; set; } + + public virtual PipeFactory PipeFactory { get; } + public virtual IScheduler InputWriterScheduler { get; } + public virtual IScheduler OutputReaderScheduler { get; } + + public IPipeConnection Transport { get; set; } + public IConnectionApplicationFeature Application => (IConnectionApplicationFeature)_currentIConnectionApplicationFeature; + } +} diff --git a/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj b/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj index 64b219c6c4..3ecaa3a789 100644 --- a/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj +++ b/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj @@ -16,15 +16,7 @@ - - - - - - - - - + diff --git a/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs b/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs index 026c0a7fa3..db47620cd8 100644 --- a/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs +++ b/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs @@ -5,15 +5,16 @@ using System; using System.Buffers; using System.Diagnostics; using System.IO; -using System.Threading.Tasks; using System.IO.Pipelines; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { - public class LibuvConnection : LibuvConnectionContext + public partial class LibuvConnection : LibuvConnectionContext { private const int MinAllocBufferSize = 2048; @@ -24,7 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal (handle, suggestedsize, state) => AllocCallback(handle, suggestedsize, state); private readonly UvStreamHandle _socket; - private IConnectionContext _connectionContext; private WritableBuffer? _currentWritableBuffer; private BufferHandle _bufferHandle; @@ -35,14 +35,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal if (_socket is UvTcpHandle tcpHandle) { - RemoteEndPoint = tcpHandle.GetPeerIPEndPoint(); - LocalEndPoint = tcpHandle.GetSockIPEndPoint(); + var remoteEndPoint = tcpHandle.GetPeerIPEndPoint(); + var localEndPoint = tcpHandle.GetSockIPEndPoint(); + + RemoteAddress = remoteEndPoint.Address; + RemotePort = remoteEndPoint.Port; + + LocalAddress = localEndPoint.Address; + LocalPort = localEndPoint.Port; } } - public string ConnectionId { get; set; } - public IPipeWriter Input { get; set; } - public LibuvOutputConsumer Output { get; set; } + public IPipeWriter Input => Application.Connection.Output; + public IPipeReader Output => Application.Connection.Input; + + public LibuvOutputConsumer OutputConsumer { get; set; } private ILibuvTrace Log => ListenerContext.TransportContext.Log; private IConnectionHandler ConnectionHandler => ListenerContext.TransportContext.ConnectionHandler; @@ -52,11 +59,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { try { - _connectionContext = ConnectionHandler.OnConnection(this); - ConnectionId = _connectionContext.ConnectionId; + ConnectionHandler.OnConnection(this); - Input = _connectionContext.Input; - Output = new LibuvOutputConsumer(_connectionContext.Output, Thread, _socket, ConnectionId, Log); + OutputConsumer = new LibuvOutputConsumer(Output, Thread, _socket, ConnectionId, Log); StartReading(); @@ -67,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal // This *must* happen after socket.ReadStart // The socket output consumer is the only thing that can close the connection. If the // output pipe is already closed by the time we start then it's fine since, it'll close gracefully afterwards. - await Output.WriteOutputAsync(); + await OutputConsumer.WriteOutputAsync(); } catch (UvException ex) { @@ -77,8 +82,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { // Now, complete the input so that no more reads can happen Input.Complete(error ?? new ConnectionAbortedException()); - _connectionContext.Output.Complete(error); - _connectionContext.OnConnectionClosed(error); + Output.Complete(error); + Application.OnConnectionClosed(error); // Make sure it isn't possible for a paused read to resume reading after calling uv_close // on the stream handle @@ -173,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal } } - _connectionContext.Abort(error); + Application.Abort(error); // Complete after aborting the connection Input.Complete(error); } @@ -211,7 +216,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal Log.ConnectionReadFin(ConnectionId); var error = new IOException(ex.Message, ex); - _connectionContext.Abort(error); + Application.Abort(error); Input.Complete(error); } } diff --git a/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs b/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs index af43c47d3a..fafb3aad57 100644 --- a/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs +++ b/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs @@ -7,24 +7,17 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { - public class LibuvConnectionContext : IConnectionInformation + public class LibuvConnectionContext : TransportConnection { - public LibuvConnectionContext() - { - } - public LibuvConnectionContext(ListenerContext context) { ListenerContext = context; } public ListenerContext ListenerContext { get; set; } - - public IPEndPoint RemoteEndPoint { get; set; } - public IPEndPoint LocalEndPoint { get; set; } - - public PipeFactory PipeFactory => ListenerContext.Thread.PipeFactory; - public IScheduler InputWriterScheduler => ListenerContext.Thread; - public IScheduler OutputReaderScheduler => ListenerContext.Thread; + + public override PipeFactory PipeFactory => ListenerContext.Thread.PipeFactory; + public override IScheduler InputWriterScheduler => ListenerContext.Thread; + public override IScheduler OutputReaderScheduler => ListenerContext.Thread; } } \ No newline at end of file diff --git a/src/Kestrel.Transport.Libuv/LibuvTransport.cs b/src/Kestrel.Transport.Libuv/LibuvTransport.cs index 5948bdc583..f3d376c3f3 100644 --- a/src/Kestrel.Transport.Libuv/LibuvTransport.cs +++ b/src/Kestrel.Transport.Libuv/LibuvTransport.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; diff --git a/src/Kestrel.Transport.Sockets/SocketConnection.cs b/src/Kestrel.Transport.Sockets/SocketConnection.cs index 0070f7af89..86f5df0d4f 100644 --- a/src/Kestrel.Transport.Sockets/SocketConnection.cs +++ b/src/Kestrel.Transport.Sockets/SocketConnection.cs @@ -10,16 +10,15 @@ using System.Net.Sockets; using System.Threading.Tasks; using System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Protocols; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { - internal sealed class SocketConnection : IConnectionInformation + internal sealed class SocketConnection : TransportConnection { private readonly Socket _socket; private readonly SocketTransport _transport; - private readonly IPEndPoint _localEndPoint; - private readonly IPEndPoint _remoteEndPoint; - private IConnectionContext _connectionContext; + private IPipeWriter _input; private IPipeReader _output; private IList> _sendBufferList; @@ -33,18 +32,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets _socket = socket; _transport = transport; - _localEndPoint = (IPEndPoint)_socket.LocalEndPoint; - _remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint; + var localEndPoint = (IPEndPoint)_socket.LocalEndPoint; + var remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint; + + LocalAddress = localEndPoint.Address; + LocalPort = localEndPoint.Port; + + RemoteAddress = remoteEndPoint.Address; + RemotePort = remoteEndPoint.Port; } public async Task StartAsync(IConnectionHandler connectionHandler) { try { - _connectionContext = connectionHandler.OnConnection(this); + connectionHandler.OnConnection(this); - _input = _connectionContext.Input; - _output = _connectionContext.Output; + _input = Application.Connection.Output; + _output = Application.Connection.Input; // Spawn send and receive logic Task receiveTask = DoReceive(); @@ -130,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets } finally { - _connectionContext.Abort(error); + Application.Abort(error); _input.Complete(error); } } @@ -224,7 +229,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets } finally { - _connectionContext.OnConnectionClosed(error); + Application.OnConnectionClosed(error); _output.Complete(error); } } @@ -239,14 +244,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets return segment; } - public IPEndPoint RemoteEndPoint => _remoteEndPoint; - - public IPEndPoint LocalEndPoint => _localEndPoint; - - public PipeFactory PipeFactory => _transport.TransportFactory.PipeFactory; - - public IScheduler InputWriterScheduler => InlineScheduler.Default; - - public IScheduler OutputReaderScheduler => TaskRunScheduler.Default; + public override PipeFactory PipeFactory => _transport.TransportFactory.PipeFactory; + public override IScheduler InputWriterScheduler => InlineScheduler.Default; + public override IScheduler OutputReaderScheduler => TaskRunScheduler.Default; } } diff --git a/src/Kestrel.Transport.Sockets/SocketTransport.cs b/src/Kestrel.Transport.Sockets/SocketTransport.cs index fd6d3b57da..83a4cb2909 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransport.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransport.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets diff --git a/src/Protocols.Abstractions/ConnectionContext.cs b/src/Protocols.Abstractions/ConnectionContext.cs new file mode 100644 index 0000000000..ebbcb458a1 --- /dev/null +++ b/src/Protocols.Abstractions/ConnectionContext.cs @@ -0,0 +1,17 @@ +using System; +using System.IO.Pipelines; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Protocols +{ + public abstract class ConnectionContext + { + public abstract string ConnectionId { get; set; } + + public abstract IFeatureCollection Features { get; } + + public abstract IPipeConnection Transport { get; set; } + + public abstract PipeFactory PipeFactory { get; } + } +} diff --git a/src/Protocols.Abstractions/ConnectionDelegate.cs b/src/Protocols.Abstractions/ConnectionDelegate.cs new file mode 100644 index 0000000000..a0656c0496 --- /dev/null +++ b/src/Protocols.Abstractions/ConnectionDelegate.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Protocols +{ + public delegate Task ConnectionDelegate(ConnectionContext connection); +} diff --git a/src/Protocols.Abstractions/DefaultConnectionContext.cs b/src/Protocols.Abstractions/DefaultConnectionContext.cs new file mode 100644 index 0000000000..0a759630bf --- /dev/null +++ b/src/Protocols.Abstractions/DefaultConnectionContext.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Text; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols.Features; + +namespace Microsoft.AspNetCore.Protocols +{ + public class DefaultConnectionContext : ConnectionContext + { + private FeatureReferences _features; + + public DefaultConnectionContext(IFeatureCollection features) + { + _features = new FeatureReferences(features); + } + + private IConnectionIdFeature ConnectionIdFeature => + _features.Fetch(ref _features.Cache.ConnectionId, _ => null); + + private IConnectionTransportFeature ConnectionTransportFeature => + _features.Fetch(ref _features.Cache.ConnectionTransport, _ => null); + + public override string ConnectionId + { + get => ConnectionIdFeature.ConnectionId; + set => ConnectionIdFeature.ConnectionId = value; + } + + public override IFeatureCollection Features => _features.Collection; + + public override PipeFactory PipeFactory => ConnectionTransportFeature.PipeFactory; + + public override IPipeConnection Transport + { + get => ConnectionTransportFeature.Connection; + set => ConnectionTransportFeature.Connection = value; + } + + struct FeatureInterfaces + { + public IConnectionIdFeature ConnectionId; + + public IConnectionTransportFeature ConnectionTransport; + } + } +} diff --git a/src/Kestrel.Transport.Abstractions/Exceptions/AddressInUseException.cs b/src/Protocols.Abstractions/Exceptions/AddressInUseException.cs similarity index 85% rename from src/Kestrel.Transport.Abstractions/Exceptions/AddressInUseException.cs rename to src/Protocols.Abstractions/Exceptions/AddressInUseException.cs index 72e1a5d774..3a0dddc671 100644 --- a/src/Kestrel.Transport.Abstractions/Exceptions/AddressInUseException.cs +++ b/src/Protocols.Abstractions/Exceptions/AddressInUseException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Protocols { public class AddressInUseException : InvalidOperationException { diff --git a/src/Kestrel.Transport.Abstractions/Exceptions/ConnectionAbortedException.cs b/src/Protocols.Abstractions/Exceptions/ConnectionAbortedException.cs similarity index 84% rename from src/Kestrel.Transport.Abstractions/Exceptions/ConnectionAbortedException.cs rename to src/Protocols.Abstractions/Exceptions/ConnectionAbortedException.cs index 817d9f9226..b1119b9c2e 100644 --- a/src/Kestrel.Transport.Abstractions/Exceptions/ConnectionAbortedException.cs +++ b/src/Protocols.Abstractions/Exceptions/ConnectionAbortedException.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Protocols { public class ConnectionAbortedException : OperationCanceledException { diff --git a/src/Kestrel.Transport.Abstractions/Exceptions/ConnectionResetException.cs b/src/Protocols.Abstractions/Exceptions/ConnectionResetException.cs similarity index 86% rename from src/Kestrel.Transport.Abstractions/Exceptions/ConnectionResetException.cs rename to src/Protocols.Abstractions/Exceptions/ConnectionResetException.cs index 1e0bef192d..a19379628c 100644 --- a/src/Kestrel.Transport.Abstractions/Exceptions/ConnectionResetException.cs +++ b/src/Protocols.Abstractions/Exceptions/ConnectionResetException.cs @@ -4,7 +4,7 @@ using System; using System.IO; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +namespace Microsoft.AspNetCore.Protocols { public class ConnectionResetException : IOException { diff --git a/src/Protocols.Abstractions/Features/IConnectionApplicationFeature.cs b/src/Protocols.Abstractions/Features/IConnectionApplicationFeature.cs new file mode 100644 index 0000000000..a610cd06d6 --- /dev/null +++ b/src/Protocols.Abstractions/Features/IConnectionApplicationFeature.cs @@ -0,0 +1,18 @@ +using System; +using System.IO.Pipelines; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IConnectionApplicationFeature + { + IPipeConnection Connection { get; set; } + + // TODO: Remove these (https://github.com/aspnet/KestrelHttpServer/issues/1772) + // REVIEW: These are around for now because handling pipe events messes with the order + // of operations an that breaks tons of tests. Instead, we preserve the existing semantics + // and ordering. + void Abort(Exception exception); + + void OnConnectionClosed(Exception exception); + } +} diff --git a/src/Protocols.Abstractions/Features/IConnectionIdFeature.cs b/src/Protocols.Abstractions/Features/IConnectionIdFeature.cs new file mode 100644 index 0000000000..1b94c5b4cc --- /dev/null +++ b/src/Protocols.Abstractions/Features/IConnectionIdFeature.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IConnectionIdFeature + { + string ConnectionId { get; set; } + } +} diff --git a/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs b/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs new file mode 100644 index 0000000000..463797265e --- /dev/null +++ b/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Text; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IConnectionTransportFeature + { + PipeFactory PipeFactory { get; } + + IPipeConnection Connection { get; set; } + + IScheduler InputWriterScheduler { get; } + + IScheduler OutputReaderScheduler { get; } + } +} diff --git a/src/Protocols.Abstractions/IConnectionBuilder.cs b/src/Protocols.Abstractions/IConnectionBuilder.cs new file mode 100644 index 0000000000..7f75d27643 --- /dev/null +++ b/src/Protocols.Abstractions/IConnectionBuilder.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.AspNetCore.Protocols +{ + public interface IConnectionBuilder + { + IServiceProvider ApplicationServices { get; } + + IConnectionBuilder Use(Func middleware); + + ConnectionDelegate Build(); + } +} diff --git a/src/Protocols.Abstractions/PipeConnection.cs b/src/Protocols.Abstractions/PipeConnection.cs new file mode 100644 index 0000000000..31b3a7a74c --- /dev/null +++ b/src/Protocols.Abstractions/PipeConnection.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.IO.Pipelines +{ + public class PipeConnection : IPipeConnection + { + public PipeConnection(IPipeReader reader, IPipeWriter writer) + { + Input = reader; + Output = writer; + } + + public IPipeReader Input { get; } + + public IPipeWriter Output { get; } + + public void Dispose() + { + } + } +} diff --git a/src/Protocols.Abstractions/Protocols.Abstractions.csproj b/src/Protocols.Abstractions/Protocols.Abstractions.csproj new file mode 100644 index 0000000000..233f0c3a2b --- /dev/null +++ b/src/Protocols.Abstractions/Protocols.Abstractions.csproj @@ -0,0 +1,30 @@ + + + + + Microsoft.AspNetCore.Protocols.Abstractions + Microsoft.AspNetCore.Protocols.Abstractions + Core components of ASP.NET Core networking protocol stack. + netstandard2.0 + true + aspnetcore + true + CS1591;$(NoWarn) + false + + CurrentRuntime + + + + + + + + + + + + + + + diff --git a/test/Kestrel.Core.Tests/AddressBinderTests.cs b/test/Kestrel.Core.Tests/AddressBinderTests.cs index 27c6d8c082..2c957e6030 100644 --- a/test/Kestrel.Core.Tests/AddressBinderTests.cs +++ b/test/Kestrel.Core.Tests/AddressBinderTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Threading.Tasks; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; diff --git a/test/Kestrel.Core.Tests/FrameConnectionTests.cs b/test/Kestrel.Core.Tests/FrameConnectionTests.cs index 555838c820..5b7afe2176 100644 --- a/test/Kestrel.Core.Tests/FrameConnectionTests.cs +++ b/test/Kestrel.Core.Tests/FrameConnectionTests.cs @@ -30,10 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { ConnectionId = "0123456789", ConnectionAdapters = new List(), - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = _pipeFactory - }, + PipeFactory = _pipeFactory, FrameConnectionId = long.MinValue, Input = _pipeFactory.Create(), Output = _pipeFactory.Create(), diff --git a/test/Kestrel.Core.Tests/FrameResponseHeadersTests.cs b/test/Kestrel.Core.Tests/FrameResponseHeadersTests.cs index dabf1d85ee..8cd807bc18 100644 --- a/test/Kestrel.Core.Tests/FrameResponseHeadersTests.cs +++ b/test/Kestrel.Core.Tests/FrameResponseHeadersTests.cs @@ -21,10 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var frameContext = new FrameContext { ServiceContext = new TestServiceContext(), - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = new PipeFactory() - }, + PipeFactory = new PipeFactory(), TimeoutControl = null }; diff --git a/test/Kestrel.Core.Tests/FrameTests.cs b/test/Kestrel.Core.Tests/FrameTests.cs index 081861975d..0f2682fcf6 100644 --- a/test/Kestrel.Core.Tests/FrameTests.cs +++ b/test/Kestrel.Core.Tests/FrameTests.cs @@ -60,10 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _frameContext = new FrameContext { ServiceContext = _serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = _pipelineFactory - }, + PipeFactory = _pipelineFactory, TimeoutControl = _timeoutControl.Object, Input = _input.Reader, Output = output diff --git a/test/Kestrel.Core.Tests/TestInput.cs b/test/Kestrel.Core.Tests/TestInput.cs index ccd07a034a..018dcb1117 100644 --- a/test/Kestrel.Core.Tests/TestInput.cs +++ b/test/Kestrel.Core.Tests/TestInput.cs @@ -26,10 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { ServiceContext = new TestServiceContext(), Input = Pipe.Reader, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = _pipelineFactory - }, + PipeFactory = _pipelineFactory, TimeoutControl = Mock.Of() }; diff --git a/test/Kestrel.FunctionalTests/RequestTests.cs b/test/Kestrel.FunctionalTests/RequestTests.cs index 8d8f997b03..7cf85fa7ce 100644 --- a/test/Kestrel.FunctionalTests/RequestTests.cs +++ b/test/Kestrel.FunctionalTests/RequestTests.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; diff --git a/test/Kestrel.Performance/FrameFeatureCollection.cs b/test/Kestrel.Performance/FrameFeatureCollection.cs index 6f1ee3d2bf..ca85ab5aca 100644 --- a/test/Kestrel.Performance/FrameFeatureCollection.cs +++ b/test/Kestrel.Performance/FrameFeatureCollection.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [Benchmark] public IHttpSendFileFeature GetLastViaGeneric() { - return _collection.Get (); + return _collection.Get(); } private object Get(Type type) @@ -85,10 +85,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var frameContext = new FrameContext { ServiceContext = serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = new PipeFactory() - } + PipeFactory = new PipeFactory() }; _frame = new Frame(application: null, frameContext: frameContext); diff --git a/test/Kestrel.Performance/FrameParsingOverheadBenchmark.cs b/test/Kestrel.Performance/FrameParsingOverheadBenchmark.cs index d570ee1f8d..ab6259e04a 100644 --- a/test/Kestrel.Performance/FrameParsingOverheadBenchmark.cs +++ b/test/Kestrel.Performance/FrameParsingOverheadBenchmark.cs @@ -29,10 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var frameContext = new FrameContext { ServiceContext = serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = new PipeFactory() - }, + PipeFactory = new PipeFactory(), TimeoutControl = new MockTimeoutControl() }; diff --git a/test/Kestrel.Performance/FrameWritingBenchmark.cs b/test/Kestrel.Performance/FrameWritingBenchmark.cs index 664aebe15c..940b5d2f8c 100644 --- a/test/Kestrel.Performance/FrameWritingBenchmark.cs +++ b/test/Kestrel.Performance/FrameWritingBenchmark.cs @@ -90,10 +90,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var frame = new TestFrame(application: null, context: new FrameContext { ServiceContext = serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = pipeFactory - }, + PipeFactory = pipeFactory, Input = input.Reader, Output = output }); diff --git a/test/Kestrel.Performance/Mocks/MockConnectionInformation.cs b/test/Kestrel.Performance/Mocks/MockConnectionInformation.cs deleted file mode 100644 index 2f01186143..0000000000 --- a/test/Kestrel.Performance/Mocks/MockConnectionInformation.cs +++ /dev/null @@ -1,20 +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.Net; -using System.IO.Pipelines; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; - -namespace Microsoft.AspNetCore.Server.Kestrel.Performance -{ - public class MockConnectionInformation : IConnectionInformation - { - public IPEndPoint RemoteEndPoint { get; } - public IPEndPoint LocalEndPoint { get; } - - public PipeFactory PipeFactory { get; set; } - public bool RequiresDispatch { get; } - public IScheduler InputWriterScheduler { get; } - public IScheduler OutputReaderScheduler { get; } - } -} diff --git a/test/Kestrel.Performance/RequestParsingBenchmark.cs b/test/Kestrel.Performance/RequestParsingBenchmark.cs index f155b2a86d..06f3dac080 100644 --- a/test/Kestrel.Performance/RequestParsingBenchmark.cs +++ b/test/Kestrel.Performance/RequestParsingBenchmark.cs @@ -33,10 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var frameContext = new FrameContext { ServiceContext = serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = PipeFactory - }, + PipeFactory = PipeFactory, TimeoutControl = new MockTimeoutControl() }; diff --git a/test/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs b/test/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs index 8423f95c02..57d9b53f70 100644 --- a/test/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs +++ b/test/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs @@ -177,10 +177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var frameContext = new FrameContext { ServiceContext = serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = new PipeFactory() - } + PipeFactory = new PipeFactory() }; var frame = new Frame(application: null, frameContext: frameContext); diff --git a/test/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs b/test/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs index 85929d7211..abef01feb0 100644 --- a/test/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs +++ b/test/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs @@ -124,10 +124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance var frame = new TestFrame(application: null, context: new FrameContext { ServiceContext = serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = pipeFactory - }, + PipeFactory = pipeFactory, TimeoutControl = new MockTimeoutControl(), Input = input.Reader, Output = output diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs index 973b0e387c..5bf0ff1b3a 100644 --- a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs @@ -699,10 +699,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var frame = new Frame(null, new FrameContext { ServiceContext = serviceContext, - ConnectionInformation = new MockConnectionInformation - { - PipeFactory = _pipeFactory - }, + PipeFactory = _pipeFactory, TimeoutControl = Mock.Of(), Output = pipe }); diff --git a/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs b/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs index a8096d7181..5b3480b0e6 100644 --- a/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs +++ b/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs @@ -3,6 +3,9 @@ using System; using System.IO.Pipelines; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols; +using Microsoft.AspNetCore.Protocols.Features; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers @@ -12,26 +15,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers public PipeOptions InputOptions { get; set; } = new PipeOptions(); public PipeOptions OutputOptions { get; set; } = new PipeOptions(); - public IConnectionContext OnConnection(IConnectionInformation connectionInfo) + public void OnConnection(IFeatureCollection features) { - Input = connectionInfo.PipeFactory.Create(InputOptions ?? new PipeOptions()); - Output = connectionInfo.PipeFactory.Create(OutputOptions ?? new PipeOptions()); + var connectionContext = new DefaultConnectionContext(features); - return new TestConnectionContext + Input = connectionContext.PipeFactory.Create(InputOptions ?? new PipeOptions()); + Output = connectionContext.PipeFactory.Create(OutputOptions ?? new PipeOptions()); + + var context = new TestConnectionContext { - Input = Input.Writer, - Output = Output.Reader, + Connection = new PipeConnection(Output.Reader, Input.Writer) }; + + connectionContext.Features.Set(context); } public IPipe Input { get; private set; } public IPipe Output { get; private set; } - - private class TestConnectionContext : IConnectionContext + + private class TestConnectionContext : IConnectionApplicationFeature { public string ConnectionId { get; } - public IPipeWriter Input { get; set; } - public IPipeReader Output { get; set; } + public IPipeConnection Connection { get; set; } public void Abort(Exception ex) { diff --git a/test/shared/MockConnectionInformation.cs b/test/shared/MockConnectionInformation.cs deleted file mode 100644 index db6ee00fda..0000000000 --- a/test/shared/MockConnectionInformation.cs +++ /dev/null @@ -1,22 +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 System.Net; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; - -namespace Microsoft.AspNetCore.Testing -{ - public class MockConnectionInformation : IConnectionInformation - { - public IPEndPoint RemoteEndPoint { get; } - - public IPEndPoint LocalEndPoint { get; } - - public PipeFactory PipeFactory { get; set; } - - public IScheduler InputWriterScheduler { get; } - - public IScheduler OutputReaderScheduler { get; } - } -}