diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln
index 1df13131e..24a8e7902 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.26228.4
+VisualStudioVersion = 15.0.26228.9
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject
@@ -40,7 +40,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0EF2AC
test\shared\TestServiceContext.cs = test\shared\TestServiceContext.cs
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel", "src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj", "{F510611A-3BEE-4B88-A613-5F4A74ED82A1}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Core", "src\Microsoft.AspNetCore.Server.Kestrel.Core\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", "{F510611A-3BEE-4B88-A613-5F4A74ED82A1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.KestrelTests", "test\Microsoft.AspNetCore.Server.KestrelTests\Microsoft.AspNetCore.Server.KestrelTests.csproj", "{37F3BFB2-6454-49E5-9D7F-581BF755CCFE}"
EndProject
@@ -61,6 +61,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestResources", "TestResour
test\shared\TestResources\testCert.pfx = test\shared\TestResources\testCert.pfx
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv", "src\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj", "{A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel", "src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj", "{56139957-5C29-4E7D-89BD-7D20598B4EAF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -167,6 +171,30 @@ Global
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x64.Build.0 = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x86.ActiveCfg = Release|Any CPU
{EBFE9719-A44B-4978-A71F-D5C254E7F35A}.Release|x86.Build.0 = Release|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x64.Build.0 = Debug|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Debug|x86.Build.0 = Debug|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x64.ActiveCfg = Release|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x64.Build.0 = Release|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x86.ActiveCfg = Release|Any CPU
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4}.Release|x86.Build.0 = Release|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x64.Build.0 = Debug|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Debug|x86.Build.0 = Debug|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x64.ActiveCfg = Release|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x64.Build.0 = Release|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x86.ActiveCfg = Release|Any CPU
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -182,5 +210,7 @@ Global
{9559A5F1-080C-4909-B6CF-7E4B3DC55748} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{EBFE9719-A44B-4978-A71F-D5C254E7F35A} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{2822C132-BFFB-4D53-AC5B-E7E47DD81A6E} = {0EF2ACDF-012F-4472-A13A-4272419E2903}
+ {A76B8C8C-0DC5-4DD3-9B1F-02E51A0915F4} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
+ {56139957-5C29-4E7D-89BD-7D20598B4EAF} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
EndGlobalSection
EndGlobal
diff --git a/samples/SampleApp/SampleApp.csproj b/samples/SampleApp/SampleApp.csproj
index 1c71f51d3..b62345639 100644
--- a/samples/SampleApp/SampleApp.csproj
+++ b/samples/SampleApp/SampleApp.csproj
@@ -12,6 +12,7 @@
+
diff --git a/samples/SampleApp/Startup.cs b/samples/SampleApp/Startup.cs
index f8d32ebe1..4a6f015a4 100644
--- a/samples/SampleApp/Startup.cs
+++ b/samples/SampleApp/Startup.cs
@@ -61,9 +61,11 @@ public static void Main(string[] args)
// The following section should be used to demo sockets
//options.ListenUnixSocket("/tmp/kestrel-test.sock");
-
+ })
+ .UseLibuv(options =>
+ {
// Uncomment the following line to change the default number of libuv threads for all endpoints.
- //options.ThreadCount = 4;
+ options.ThreadCount = 4;
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup()
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/ConnectionAdapterContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/ConnectionAdapterContext.cs
similarity index 100%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/ConnectionAdapterContext.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/ConnectionAdapterContext.cs
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/IAdaptedConnection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IAdaptedConnection.cs
similarity index 100%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/IAdaptedConnection.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IAdaptedConnection.cs
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/IConnectionAdapter.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IConnectionAdapter.cs
similarity index 95%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/IConnectionAdapter.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IConnectionAdapter.cs
index cbdb12b3b..65140d195 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/IConnectionAdapter.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/IConnectionAdapter.cs
@@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/AdaptedPipeline.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs
similarity index 78%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/AdaptedPipeline.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs
index 488d902d9..54674ae5d 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/AdaptedPipeline.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs
@@ -9,7 +9,7 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{
- public class AdaptedPipeline : IDisposable
+ public class AdaptedPipeline
{
private const int MinAllocBufferSize = 2048;
@@ -31,30 +31,16 @@ public AdaptedPipeline(
public ISocketOutput Output => _output;
- public void Dispose()
- {
- Input.Writer.Complete();
- }
-
- public async Task StartAsync()
+ public async Task RunAsync()
{
var inputTask = ReadInputAsync();
var outputTask = _output.WriteOutputAsync();
- var result = await Task.WhenAny(inputTask, outputTask);
+ await inputTask;
- if (result == inputTask)
- {
- // Close output
- _output.Dispose();
- }
- else
- {
- // Close input
- Input.Writer.Complete();
- }
+ _output.Dispose();
- await Task.WhenAll(inputTask, outputTask);
+ await outputTask;
}
private async Task ReadInputAsync()
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/LoggingStream.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/LoggingStream.cs
similarity index 100%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/LoggingStream.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/LoggingStream.cs
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/RawStream.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/RawStream.cs
similarity index 99%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/RawStream.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/RawStream.cs
index 7433d5eb7..6e2195857 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/RawStream.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/RawStream.cs
@@ -7,7 +7,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
-using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/StreamSocketOutput.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/StreamSocketOutput.cs
similarity index 98%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/StreamSocketOutput.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/StreamSocketOutput.cs
index 34ef6ae31..11da4bc24 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/Internal/StreamSocketOutput.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/Internal/StreamSocketOutput.cs
@@ -16,7 +16,7 @@ public class StreamSocketOutput : ISocketOutput
private readonly Stream _outputStream;
private readonly IPipe _pipe;
- private object _sync = new object();
+ private readonly object _sync = new object();
private bool _completed;
public StreamSocketOutput(Stream outputStream, IPipe pipe)
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/ListenOptionsConnectionLoggingExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/ListenOptionsConnectionLoggingExtensions.cs
similarity index 100%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/ListenOptionsConnectionLoggingExtensions.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/ListenOptionsConnectionLoggingExtensions.cs
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Adapter/LoggingConnectionAdapter.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/LoggingConnectionAdapter.cs
similarity index 100%
rename from src/Microsoft.AspNetCore.Server.Kestrel/Adapter/LoggingConnectionAdapter.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/Adapter/LoggingConnectionAdapter.cs
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/BadHttpRequestException.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/BadHttpRequestException.cs
similarity index 100%
rename from src/Microsoft.AspNetCore.Server.Kestrel/BadHttpRequestException.cs
rename to src/Microsoft.AspNetCore.Server.Kestrel.Core/BadHttpRequestException.cs
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs
new file mode 100644
index 000000000..9a346f180
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/ConnectionHandler.cs
@@ -0,0 +1,128 @@
+// 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;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Transport;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Internal
+{
+ public class ConnectionHandler : IConnectionHandler
+ {
+ // Base32 encoding - in ascii sort order for easy text based sorting
+ private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+
+ // Seed the _lastConnectionId for this application instance with
+ // the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001
+ // for a roughly increasing _requestId over restarts
+ private static long _lastConnectionId = DateTime.UtcNow.Ticks;
+
+ private readonly ServiceContext _serviceContext;
+ private readonly IHttpApplication _application;
+
+ public ConnectionHandler(ServiceContext serviceContext, IHttpApplication application)
+ {
+ _serviceContext = serviceContext;
+ _application = application;
+ }
+
+ public IConnectionContext OnConnection(IConnectionInformation connectionInfo)
+ {
+ var inputPipe = connectionInfo.PipeFactory.Create(GetInputPipeOptions(connectionInfo.InputWriterScheduler));
+ var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(connectionInfo.OutputWriterScheduler));
+
+ var connectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));
+
+ var frameContext = new FrameContext
+ {
+ ConnectionId = connectionId,
+ ConnectionInformation = connectionInfo,
+ ServiceContext = _serviceContext
+ };
+
+ // TODO: Untangle this mess
+ var frame = new Frame(_application, frameContext);
+ var outputProducer = new SocketOutputProducer(outputPipe.Writer, frame, connectionId, _serviceContext.Log);
+ frame.LifetimeControl = new ConnectionLifetimeControl(connectionId, outputPipe.Reader, outputProducer, _serviceContext.Log);
+
+ var connection = new FrameConnection(new FrameConnectionContext
+ {
+ ConnectionId = connectionId,
+ ServiceContext = _serviceContext,
+ PipeFactory = connectionInfo.PipeFactory,
+ ConnectionAdapters = connectionInfo.ListenOptions.ConnectionAdapters,
+ Frame = frame,
+ Input = inputPipe,
+ Output = outputPipe,
+ OutputProducer = outputProducer
+ });
+
+ // Since data cannot be added to the inputPipe by the transport until OnConnection returns,
+ // Frame.RequestProcessingAsync is guaranteed to unblock the transport thread before calling
+ // application code.
+ connection.StartRequestProcessing();
+
+ return connection;
+ }
+
+ // Internal for testing
+ internal PipeOptions GetInputPipeOptions(IScheduler writerScheduler) => new PipeOptions
+ {
+ ReaderScheduler = _serviceContext.ThreadPool,
+ WriterScheduler = writerScheduler,
+ MaximumSizeHigh = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
+ MaximumSizeLow = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0
+ };
+
+ internal PipeOptions GetOutputPipeOptions(IScheduler readerScheduler) => new PipeOptions
+ {
+ ReaderScheduler = readerScheduler,
+ WriterScheduler = _serviceContext.ThreadPool,
+ MaximumSizeHigh = GetOutputResponseBufferSize(),
+ MaximumSizeLow = GetOutputResponseBufferSize()
+ };
+
+ private long GetOutputResponseBufferSize()
+ {
+ var bufferSize = _serviceContext.ServerOptions.Limits.MaxResponseBufferSize;
+ if (bufferSize == 0)
+ {
+ // 0 = no buffering so we need to configure the pipe so the the writer waits on the reader directly
+ return 1;
+ }
+
+ // null means that we have no back pressure
+ return bufferSize ?? 0;
+ }
+
+ private static unsafe string GenerateConnectionId(long id)
+ {
+ // The following routine is ~310% faster than calling long.ToString() on x64
+ // and ~600% faster than calling long.ToString() on x86 in tight loops of 1 million+ iterations
+ // See: https://github.com/aspnet/Hosting/pull/385
+
+ // stackalloc to allocate array on stack rather than heap
+ char* charBuffer = stackalloc char[13];
+
+ charBuffer[0] = _encode32Chars[(int)(id >> 60) & 31];
+ charBuffer[1] = _encode32Chars[(int)(id >> 55) & 31];
+ charBuffer[2] = _encode32Chars[(int)(id >> 50) & 31];
+ charBuffer[3] = _encode32Chars[(int)(id >> 45) & 31];
+ charBuffer[4] = _encode32Chars[(int)(id >> 40) & 31];
+ charBuffer[5] = _encode32Chars[(int)(id >> 35) & 31];
+ charBuffer[6] = _encode32Chars[(int)(id >> 30) & 31];
+ charBuffer[7] = _encode32Chars[(int)(id >> 25) & 31];
+ charBuffer[8] = _encode32Chars[(int)(id >> 20) & 31];
+ charBuffer[9] = _encode32Chars[(int)(id >> 15) & 31];
+ charBuffer[10] = _encode32Chars[(int)(id >> 10) & 31];
+ charBuffer[11] = _encode32Chars[(int)(id >> 5) & 31];
+ charBuffer[12] = _encode32Chars[(int)id & 31];
+
+ // string ctor overload that takes char*
+ return new string(charBuffer, 0, 13);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnection.cs
new file mode 100644
index 000000000..e601f5f0a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnection.cs
@@ -0,0 +1,159 @@
+// 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.IO;
+using System.IO.Pipelines;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.Kestrel.Adapter;
+using Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal;
+using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
+using Microsoft.AspNetCore.Server.Kestrel.Transport;
+using Microsoft.Extensions.Internal;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Internal
+{
+ public class FrameConnection : IConnectionContext
+ {
+ private readonly FrameConnectionContext _context;
+ private readonly Frame _frame;
+ private readonly List _connectionAdapters;
+ private readonly TaskCompletionSource