diff --git a/src/Kestrel.Core/Internal/ConnectionHandler.cs b/src/Kestrel.Core/Internal/ConnectionHandler.cs index f989a61c18..db02fcd6ff 100644 --- a/src/Kestrel.Core/Internal/ConnectionHandler.cs +++ b/src/Kestrel.Core/Internal/ConnectionHandler.cs @@ -28,29 +28,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal private IKestrelTrace Log => _serviceContext.Log; - public void OnConnection(IFeatureCollection features) + public void OnConnection(TransportConnection connection) { - var connectionContext = new DefaultConnectionContext(features); - - var transportFeature = connectionContext.Features.Get(); - // REVIEW: Unfortunately, we still need to use the service context to create the pipes since the settings // for the scheduler and limits are specified here - var inputOptions = GetInputPipeOptions(_serviceContext, transportFeature.MemoryPool, transportFeature.InputWriterScheduler); - var outputOptions = GetOutputPipeOptions(_serviceContext, transportFeature.MemoryPool, transportFeature.OutputReaderScheduler); + var inputOptions = GetInputPipeOptions(_serviceContext, connection.MemoryPool, connection.InputWriterScheduler); + var outputOptions = GetOutputPipeOptions(_serviceContext, connection.MemoryPool, connection.OutputReaderScheduler); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); // Set the transport and connection id - connectionContext.ConnectionId = CorrelationIdGenerator.GetNextId(); - connectionContext.Transport = pair.Transport; + connection.ConnectionId = CorrelationIdGenerator.GetNextId(); + connection.Transport = pair.Transport; // This *must* be set before returning from OnConnection - transportFeature.Application = pair.Application; + connection.Application = pair.Application; // REVIEW: This task should be tracked by the server for graceful shutdown // Today it's handled specifically for http but not for aribitrary middleware - _ = Execute(connectionContext); + _ = Execute(new DefaultConnectionContext(connection)); } private async Task Execute(ConnectionContext connectionContext) diff --git a/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs b/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs index 3e25712899..3be295627d 100644 --- a/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs +++ b/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs @@ -33,7 +33,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { // 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 transportFeature = connectionContext.Features.Get(); + var applicationFeature = connectionContext.Features.Get(); + var memoryPoolFeature = connectionContext.Features.Get(); var httpConnectionId = Interlocked.Increment(ref _lastHttpConnectionId); @@ -44,10 +45,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal Protocols = _protocols, ServiceContext = _serviceContext, ConnectionFeatures = connectionContext.Features, - MemoryPool = transportFeature.MemoryPool, + MemoryPool = memoryPoolFeature.MemoryPool, ConnectionAdapters = _connectionAdapters, Transport = connectionContext.Transport, - Application = transportFeature.Application + Application = applicationFeature.Application }; var connectionFeature = connectionContext.Features.Get(); diff --git a/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs b/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs index 9ae3f89c3f..eec71ff61c 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/IConnectionHandler.cs @@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { public interface IConnectionHandler { - void OnConnection(IFeatureCollection features); + void OnConnection(TransportConnection connection); } } diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs index 4259da0128..275c80ba95 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs @@ -12,15 +12,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal public partial class TransportConnection : IFeatureCollection, IHttpConnectionFeature, IConnectionIdFeature, - IConnectionTransportFeature + IConnectionTransportFeature, + IMemoryPoolFeature, + IApplicationTransportFeature, + ITransportSchedulerFeature { 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 IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); + private static readonly Type IApplicationTransportFeatureType = typeof(IApplicationTransportFeature); + private static readonly Type ITransportSchedulerFeatureType = typeof(ITransportSchedulerFeature); private object _currentIHttpConnectionFeature; private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; + private object _currentIMemoryPoolFeature; + private object _currentIApplicationTransportFeature; + private object _currentITransportSchedulerFeature; private int _featureRevision; @@ -95,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal set => LocalPort = value; } - MemoryPool IConnectionTransportFeature.MemoryPool => MemoryPool; + MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool; IDuplexPipe IConnectionTransportFeature.Transport { @@ -103,12 +112,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal set => Transport = value; } - IDuplexPipe IConnectionTransportFeature.Application + IDuplexPipe IApplicationTransportFeature.Application { get => Application; set => Application = value; } + PipeScheduler ITransportSchedulerFeature.InputWriterScheduler => InputWriterScheduler; + PipeScheduler ITransportSchedulerFeature.OutputReaderScheduler => OutputReaderScheduler; + object IFeatureCollection.this[Type key] { get @@ -128,6 +140,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal return _currentIConnectionTransportFeature; } + if (key == IMemoryPoolFeatureType) + { + return _currentIMemoryPoolFeature; + } + + if (key == IApplicationTransportFeatureType) + { + return _currentIApplicationTransportFeature; + } + + if (key == ITransportSchedulerFeatureType) + { + return _currentITransportSchedulerFeature; + } + if (MaybeExtra != null) { return ExtraFeatureGet(key); @@ -151,6 +178,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { _currentIConnectionTransportFeature = value; } + else if (key == IMemoryPoolFeatureType) + { + _currentIMemoryPoolFeature = value; + } + else if (key == IApplicationTransportFeatureType) + { + _currentIApplicationTransportFeature = value; + } + else if (key == ITransportSchedulerFeatureType) + { + _currentITransportSchedulerFeature = value; + } else { ExtraFeatureSet(key, value); @@ -160,18 +199,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal TFeature IFeatureCollection.Get() { - if (typeof(TFeature) == typeof(IHttpConnectionFeature)) + if (typeof(TFeature) == IHttpConnectionFeatureType) { return (TFeature)_currentIHttpConnectionFeature; } - else if (typeof(TFeature) == typeof(IConnectionIdFeature)) + else if (typeof(TFeature) == IConnectionIdFeatureType) { return (TFeature)_currentIConnectionIdFeature; } - else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + else if (typeof(TFeature) == IConnectionTransportFeatureType) { return (TFeature)_currentIConnectionTransportFeature; } + else if (typeof(TFeature) == IMemoryPoolFeatureType) + { + return (TFeature)_currentIMemoryPoolFeature; + } + else if (typeof(TFeature) == IApplicationTransportFeatureType) + { + return (TFeature)_currentIApplicationTransportFeature; + } + else if (typeof(TFeature) == ITransportSchedulerFeatureType) + { + return (TFeature)_currentITransportSchedulerFeature; + } else if (MaybeExtra != null) { return (TFeature)ExtraFeatureGet(typeof(TFeature)); @@ -184,18 +235,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { _featureRevision++; - if (typeof(TFeature) == typeof(IHttpConnectionFeature)) + if (typeof(TFeature) == IHttpConnectionFeatureType) { _currentIHttpConnectionFeature = instance; } - else if (typeof(TFeature) == typeof(IConnectionIdFeature)) + else if (typeof(TFeature) == IConnectionIdFeatureType) { _currentIConnectionIdFeature = instance; } - else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) + else if (typeof(TFeature) == IConnectionTransportFeatureType) { _currentIConnectionTransportFeature = instance; } + else if (typeof(TFeature) == IMemoryPoolFeatureType) + { + _currentIMemoryPoolFeature = instance; + } + else if (typeof(TFeature) == IApplicationTransportFeatureType) + { + _currentIApplicationTransportFeature = instance; + } + else if (typeof(TFeature) == ITransportSchedulerFeatureType) + { + _currentITransportSchedulerFeature = instance; + } else { ExtraFeatureSet(typeof(TFeature), instance); @@ -223,6 +286,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); } + if (_currentIMemoryPoolFeature != null) + { + yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); + } + + if (_currentIApplicationTransportFeature != null) + { + yield return new KeyValuePair(IApplicationTransportFeatureType, _currentIApplicationTransportFeature); + } + + if (_currentITransportSchedulerFeature != null) + { + yield return new KeyValuePair(ITransportSchedulerFeatureType, _currentITransportSchedulerFeature); + } + if (MaybeExtra != null) { foreach (var item in MaybeExtra) diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs index 3aae48b16c..bfe7e53458 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs @@ -12,6 +12,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal _currentIConnectionIdFeature = this; _currentIConnectionTransportFeature = this; _currentIHttpConnectionFeature = this; + _currentIApplicationTransportFeature = this; + _currentIMemoryPoolFeature = this; + _currentITransportSchedulerFeature = this; } public IPAddress RemoteAddress { get; set; } diff --git a/src/Protocols.Abstractions/ConnectionBuilderExtensions.cs b/src/Protocols.Abstractions/ConnectionBuilderExtensions.cs new file mode 100644 index 0000000000..9db3e4c468 --- /dev/null +++ b/src/Protocols.Abstractions/ConnectionBuilderExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Protocols +{ + public static class ConnectionBuilderExtensions + { + public static IConnectionBuilder Use(this IConnectionBuilder connectionBuilder, Func, Task> middleware) + { + return connectionBuilder.Use(next => + { + return context => + { + Func simpleNext = () => next(context); + return middleware(context, simpleNext); + }; + }); + } + + public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder, Func middleware) + { + return connectionBuilder.Use(next => + { + return context => + { + return middleware(context); + }; + }); + } + } +} \ No newline at end of file diff --git a/src/Protocols.Abstractions/DuplexPipe.cs b/src/Protocols.Abstractions/DuplexPipe.cs index adf8b497c6..e7a6ad1710 100644 --- a/src/Protocols.Abstractions/DuplexPipe.cs +++ b/src/Protocols.Abstractions/DuplexPipe.cs @@ -14,10 +14,6 @@ namespace System.IO.Pipelines public PipeWriter Output { get; } - public void Dispose() - { - } - public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions) { var input = new Pipe(inputOptions); diff --git a/src/Protocols.Abstractions/Features/ConnectionBuilder.cs b/src/Protocols.Abstractions/Features/ConnectionBuilder.cs new file mode 100644 index 0000000000..68efce703f --- /dev/null +++ b/src/Protocols.Abstractions/Features/ConnectionBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Protocols +{ + public class ConnectionBuilder : IConnectionBuilder + { + private readonly IList> _components = new List>(); + + public IServiceProvider ApplicationServices { get; } + + public ConnectionBuilder(IServiceProvider applicationServices) + { + ApplicationServices = applicationServices; + } + + public IConnectionBuilder Use(Func middleware) + { + _components.Add(middleware); + return this; + } + + public ConnectionDelegate Build() + { + ConnectionDelegate app = features => + { + return Task.CompletedTask; + }; + + foreach (var component in _components.Reverse()) + { + app = component(app); + } + + return app; + } + } +} \ No newline at end of file diff --git a/src/Protocols.Abstractions/Features/IApplicationTransportFeature.cs b/src/Protocols.Abstractions/Features/IApplicationTransportFeature.cs new file mode 100644 index 0000000000..10f22d135e --- /dev/null +++ b/src/Protocols.Abstractions/Features/IApplicationTransportFeature.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.IO.Pipelines; +using System.Threading; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IApplicationTransportFeature + { + IDuplexPipe Application { get; set; } + } +} diff --git a/src/Protocols.Abstractions/Features/IConnectionHeartbeatFeature.cs b/src/Protocols.Abstractions/Features/IConnectionHeartbeatFeature.cs new file mode 100644 index 0000000000..9770143a34 --- /dev/null +++ b/src/Protocols.Abstractions/Features/IConnectionHeartbeatFeature.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IConnectionHeartbeatFeature + { + void OnHeartbeat(Action action, object state); + } +} \ No newline at end of file diff --git a/src/Protocols.Abstractions/Features/IConnectionIdFeature.cs b/src/Protocols.Abstractions/Features/IConnectionIdFeature.cs index ab1c09e3db..4c6cd81e77 100644 --- a/src/Protocols.Abstractions/Features/IConnectionIdFeature.cs +++ b/src/Protocols.Abstractions/Features/IConnectionIdFeature.cs @@ -1,4 +1,7 @@ -namespace Microsoft.AspNetCore.Protocols.Features +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Protocols.Features { public interface IConnectionIdFeature { diff --git a/src/Protocols.Abstractions/Features/IConnectionInherentKeepAliveFeature.cs b/src/Protocols.Abstractions/Features/IConnectionInherentKeepAliveFeature.cs new file mode 100644 index 0000000000..cc3a211e46 --- /dev/null +++ b/src/Protocols.Abstractions/Features/IConnectionInherentKeepAliveFeature.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + /// + /// Indicates if the connection transport has an "inherent keep-alive", which means that the transport will automatically + /// inform the client that it is still present. + /// + /// + /// The most common example of this feature is the Long Polling HTTP transport, which must (due to HTTP limitations) terminate + /// each poll within a particular interval and return a signal indicating "the server is still here, but there is no data yet". + /// This feature allows applications to add keep-alive functionality, but limit it only to transports that don't have some kind + /// of inherent keep-alive. + /// + public interface IConnectionInherentKeepAliveFeature + { + TimeSpan KeepAliveInterval { get; } + } +} \ No newline at end of file diff --git a/src/Protocols.Abstractions/Features/IConnectionMetadataFeature.cs b/src/Protocols.Abstractions/Features/IConnectionMetadataFeature.cs new file mode 100644 index 0000000000..b2e0f67789 --- /dev/null +++ b/src/Protocols.Abstractions/Features/IConnectionMetadataFeature.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IConnectionMetadataFeature + { + IDictionary Metadata { get; set; } + } +} \ No newline at end of file diff --git a/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs b/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs index 1a5500b692..bf7067be23 100644 --- a/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs +++ b/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs @@ -1,4 +1,7 @@ -using System.Buffers; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; using System.IO.Pipelines; using System.Threading; @@ -6,14 +9,6 @@ namespace Microsoft.AspNetCore.Protocols.Features { public interface IConnectionTransportFeature { - MemoryPool MemoryPool { get; } - IDuplexPipe Transport { get; set; } - - IDuplexPipe Application { get; set; } - - PipeScheduler InputWriterScheduler { get; } - - PipeScheduler OutputReaderScheduler { get; } } } diff --git a/src/Protocols.Abstractions/Features/IConnectionUserFeature.cs b/src/Protocols.Abstractions/Features/IConnectionUserFeature.cs new file mode 100644 index 0000000000..5daf9fd232 --- /dev/null +++ b/src/Protocols.Abstractions/Features/IConnectionUserFeature.cs @@ -0,0 +1,9 @@ +using System.Security.Claims; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IConnectionUserFeature + { + ClaimsPrincipal User { get; set; } + } +} \ No newline at end of file diff --git a/src/Protocols.Abstractions/Features/IMemoryPoolFeature.cs b/src/Protocols.Abstractions/Features/IMemoryPoolFeature.cs new file mode 100644 index 0000000000..128b9ae3c9 --- /dev/null +++ b/src/Protocols.Abstractions/Features/IMemoryPoolFeature.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.IO.Pipelines; +using System.Threading; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface IMemoryPoolFeature + { + MemoryPool MemoryPool { get; } + } +} diff --git a/src/Protocols.Abstractions/Features/ITransportSchedulerFeature.cs b/src/Protocols.Abstractions/Features/ITransportSchedulerFeature.cs new file mode 100644 index 0000000000..05c8b8d605 --- /dev/null +++ b/src/Protocols.Abstractions/Features/ITransportSchedulerFeature.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.IO.Pipelines; +using System.Threading; + +namespace Microsoft.AspNetCore.Protocols.Features +{ + public interface ITransportSchedulerFeature + { + PipeScheduler InputWriterScheduler { get; } + + PipeScheduler OutputReaderScheduler { get; } + } +} diff --git a/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs b/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs index ee1cffefdc..27128fe558 100644 --- a/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs +++ b/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs @@ -45,24 +45,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.True(((TestKestrelTrace)serviceContext.Log).Logger.Scopes.IsEmpty); } - private class TestConnection : FeatureCollection, IConnectionIdFeature, IConnectionTransportFeature + private class TestConnection : TransportConnection { - public TestConnection() - { - Set(this); - Set(this); - } + public override MemoryPool MemoryPool { get; } = KestrelMemoryPool.Create(); - public MemoryPool MemoryPool { get; } = KestrelMemoryPool.Create(); + public override PipeScheduler InputWriterScheduler => PipeScheduler.ThreadPool; - public IDuplexPipe Transport { get; set; } - public IDuplexPipe Application { get; set; } - - public PipeScheduler InputWriterScheduler => PipeScheduler.ThreadPool; - - public PipeScheduler OutputReaderScheduler => PipeScheduler.ThreadPool; - - public string ConnectionId { get; set; } + public override PipeScheduler OutputReaderScheduler => PipeScheduler.ThreadPool; } } } diff --git a/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs b/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs index daf6cdd27c..edae9162f5 100644 --- a/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs +++ b/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs @@ -16,17 +16,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers public Func, PipeOptions> InputOptions { get; set; } = pool => new PipeOptions(pool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); public Func, PipeOptions> OutputOptions { get; set; } = pool => new PipeOptions(pool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - public void OnConnection(IFeatureCollection features) + public void OnConnection(TransportConnection connection) { - var connectionContext = new DefaultConnectionContext(features); + Input = new Pipe(InputOptions(connection.MemoryPool)); + Output = new Pipe(OutputOptions(connection.MemoryPool)); - var feature = connectionContext.Features.Get(); - - Input = new Pipe(InputOptions(feature.MemoryPool)); - Output = new Pipe(OutputOptions(feature.MemoryPool)); - - connectionContext.Transport = new DuplexPipe(Input.Reader, Output.Writer); - feature.Application = new DuplexPipe(Output.Reader, Input.Writer); + connection.Transport = new DuplexPipe(Input.Reader, Output.Writer); + connection.Application = new DuplexPipe(Output.Reader, Input.Writer); } public Pipe Input { get; private set; }