Transport agnostic kestrel refactoring (#1551)

- Add transport interfaces
- Create separate Core and Libuv projects

#828
This commit is contained in:
Stephen Halter 2017-03-29 16:06:05 -07:00 committed by GitHub
parent 792b71dbcb
commit 7f785588ef
191 changed files with 2575 additions and 28167 deletions

View File

@ -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

View File

@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.Kestrel.Https\Microsoft.AspNetCore.Server.Kestrel.Https.csproj" />
</ItemGroup>

View File

@ -61,9 +61,11 @@ namespace SampleApp
// 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<Startup>()

View File

@ -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

View File

@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{
public class AdaptedPipeline : IDisposable
public class AdaptedPipeline
{
private const int MinAllocBufferSize = 2048;
@ -31,30 +31,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
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()

View File

@ -7,7 +7,6 @@ using System.IO.Pipelines;
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
{

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
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)

View File

@ -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<TContext> : 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<TContext> _application;
public ConnectionHandler(ServiceContext serviceContext, IHttpApplication<TContext> 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<TContext>(_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);
}
}
}

View File

@ -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<IConnectionAdapter> _connectionAdapters;
private readonly TaskCompletionSource<object> _frameStartedTcs = new TaskCompletionSource<object>();
private AdaptedPipeline _adaptedPipeline;
private Stream _filteredStream;
private Task _adaptedPipelineTask = TaskCache.CompletedTask;
public FrameConnection(FrameConnectionContext context)
{
_context = context;
_frame = context.Frame;
_connectionAdapters = context.ConnectionAdapters;
}
public string ConnectionId => _context.ConnectionId;
public IPipeWriter Input => _context.Input.Writer;
public IPipeReader Output => _context.Output.Reader;
private PipeFactory PipeFactory => _context.PipeFactory;
// Internal for testing
internal PipeOptions AdaptedPipeOptions => new PipeOptions
{
ReaderScheduler = InlineScheduler.Default,
WriterScheduler = InlineScheduler.Default,
MaximumSizeHigh = _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
MaximumSizeLow = _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0
};
private IKestrelTrace Log => _context.ServiceContext.Log;
public void StartRequestProcessing()
{
_frame.Input = _context.Input.Reader;
_frame.Output = _context.OutputProducer;
if (_connectionAdapters.Count == 0)
{
_frame.Start();
_frameStartedTcs.SetResult(null);
}
else
{
// Ensure that IConnectionAdapter.OnConnectionAsync does not run on the transport thread.
_context.ServiceContext.ThreadPool.UnsafeRun(state =>
{
// ApplyConnectionAdaptersAsync should never throw. If it succeeds, it will call _frame.Start().
// Otherwise, it will close the connection.
var ignore = ((FrameConnection)state).ApplyConnectionAdaptersAsync();
}, this);
}
}
public async Task StopAsync()
{
await _frameStartedTcs.Task;
await _frame.StopAsync();
await _adaptedPipelineTask;
}
public void Abort(Exception ex)
{
_frame.Abort(ex);
}
public void Timeout()
{
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
}
private async Task ApplyConnectionAdaptersAsync()
{
try
{
var rawSocketOutput = _frame.Output;
var rawStream = new RawStream(_frame.Input, rawSocketOutput);
var adapterContext = new ConnectionAdapterContext(rawStream);
var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
for (var i = 0; i < _connectionAdapters.Count; i++)
{
var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext);
adaptedConnections[i] = adaptedConnection;
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream);
}
if (adapterContext.ConnectionStream != rawStream)
{
_filteredStream = adapterContext.ConnectionStream;
_adaptedPipeline = new AdaptedPipeline(
adapterContext.ConnectionStream,
PipeFactory.Create(AdaptedPipeOptions),
PipeFactory.Create(AdaptedPipeOptions));
_frame.Input = _adaptedPipeline.Input.Reader;
_frame.Output = _adaptedPipeline.Output;
_adaptedPipelineTask = RunAdaptedPipeline();
}
_frame.AdaptedConnections = adaptedConnections;
_frame.Start();
_frameStartedTcs.SetResult(null);
}
catch (Exception ex)
{
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
_frameStartedTcs.SetResult(null);
CloseRawPipes();
}
}
private async Task RunAdaptedPipeline()
{
try
{
await _adaptedPipeline.RunAsync();
}
catch (Exception ex)
{
// adaptedPipeline.RunAsync() shouldn't throw.
Log.LogError(0, ex, $"{nameof(FrameConnection)}.{nameof(ApplyConnectionAdaptersAsync)}");
}
finally
{
CloseRawPipes();
}
}
private void CloseRawPipes()
{
_filteredStream?.Dispose();
_context.OutputProducer.Dispose();
_context.Input.Reader.Complete();
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal
{
public class FrameConnectionContext
{
public string ConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; }
public PipeFactory PipeFactory { get; set; }
public List<IConnectionAdapter> ConnectionAdapters { get; set; }
public Frame Frame { get; set; }
public SocketOutputProducer OutputProducer { get; set; }
public IPipe Input { get; set; }
public IPipe Output { get; set; }
}
}

View File

@ -4,7 +4,6 @@
using System;
using System.IO.Pipelines;
using System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{

View File

@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO.Pipelines;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class ConnectionLifetimeControl
{
public ConnectionLifetimeControl(
string connectionId,
IPipeReader outputPipeReader,
SocketOutputProducer outputProducer,
IKestrelTrace log)
{
ConnectionId = connectionId;
OutputReader = outputPipeReader;
OutputProducer = outputProducer;
Log = log;
}
private string ConnectionId { get; }
private IPipeReader OutputReader { get; }
private SocketOutputProducer OutputProducer { get; }
private IKestrelTrace Log { get; }
public void End(ProduceEndType endType)
{
switch (endType)
{
case ProduceEndType.ConnectionKeepAlive:
Log.ConnectionKeepAlive(ConnectionId);
break;
case ProduceEndType.SocketShutdown:
OutputReader.CancelPendingRead();
goto case ProduceEndType.SocketDisconnect;
case ProduceEndType.SocketDisconnect:
OutputProducer.Dispose();
Log.ConnectionDisconnect(ConnectionId);
break;
}
}
}
}

View File

@ -6,7 +6,6 @@ using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
@ -159,7 +158,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
// No requests since the last timer tick, we need to check if we're beyond the idle threshold
if ((now.Ticks - PlatformApis.VolatileRead(ref _lastRequestSeenTicks)) >= _timeWithoutRequestsUntilIdle.Ticks)
// TODO: Use PlatformApis.VolatileRead equivalent again
if ((now.Ticks - Interlocked.Read(ref _lastRequestSeenTicks)) >= _timeWithoutRequestsUntilIdle.Ticks)
{
// No requests since idle threshold so stop the timer if it's still running
StopTimer();

View File

@ -5,7 +5,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

View File

@ -16,6 +16,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
@ -52,7 +53,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
private TaskCompletionSource<object> _frameStartedTcs = new TaskCompletionSource<object>();
private Task _requestProcessingTask;
protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
protected int _requestAborted;
@ -77,37 +77,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
protected long _responseBytesWritten;
private readonly FrameContext _frameContext;
private readonly IHttpParser _parser;
public Frame(ConnectionContext context)
public Frame(FrameContext frameContext)
{
ConnectionContext = context;
Input = context.Input;
Output = context.Output;
_frameContext = frameContext;
ServerOptions = context.ListenerContext.ServiceContext.ServerOptions;
ServerOptions = ServiceContext.ServerOptions;
_parser = context.ListenerContext.ServiceContext.HttpParserFactory(this);
_parser = ServiceContext.HttpParserFactory(this);
FrameControl = this;
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
_requestHeadersTimeoutMilliseconds = (long)ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
}
public ConnectionContext ConnectionContext { get; }
public IPipe Input { get; set; }
public ServiceContext ServiceContext => _frameContext.ServiceContext;
public IConnectionInformation ConnectionInformation => _frameContext.ConnectionInformation;
public IPipeReader Input { get; set; }
public ISocketOutput Output { get; set; }
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; }
public ConnectionLifetimeControl LifetimeControl { get; set; }
protected IConnectionControl ConnectionControl => ConnectionContext.ConnectionControl;
protected IKestrelTrace Log => ConnectionContext.ListenerContext.ServiceContext.Log;
protected ITimeoutControl TimeoutControl => ConnectionInformation.TimeoutControl;
protected IKestrelTrace Log => ServiceContext.Log;
private DateHeaderValueManager DateHeaderValueManager => ConnectionContext.ListenerContext.ServiceContext.DateHeaderValueManager;
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 => ConnectionContext.LocalEndPoint;
private IPEndPoint RemoteEndPoint => ConnectionContext.RemoteEndPoint;
protected string ConnectionId => ConnectionContext.ConnectionId;
private IPEndPoint LocalEndPoint => ConnectionInformation.LocalEndPoint;
private IPEndPoint RemoteEndPoint => ConnectionInformation.RemoteEndPoint;
protected string ConnectionId => _frameContext.ConnectionId;
public string ConnectionIdFeature { get; set; }
public IPAddress RemoteIpAddress { get; set; }
@ -218,8 +220,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public Stream DuplexStream { get; set; }
public Task FrameStartedTask => _frameStartedTcs.Task;
public CancellationToken RequestAborted
{
get
@ -392,7 +392,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
Reset();
_requestProcessingTask = RequestProcessingAsync();
_frameStartedTcs.SetResult(null);
}
/// <summary>
@ -404,11 +403,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public Task StopAsync()
{
_requestProcessingStopping = true;
Input.Reader.CancelPendingRead();
Input.CancelPendingRead();
return _requestProcessingTask ?? TaskCache.CompletedTask;
}
private void CancelRequestAbortedToken()
{
try
{
RequestAbortedSource.Cancel();
_abortedCts = null;
}
catch (Exception ex)
{
Log.ApplicationError(ConnectionId, ex);
}
}
/// <summary>
/// Immediate kill the connection and poison the request and response streams.
/// </summary>
@ -421,24 +433,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_frameStreams?.RequestBody.Abort(error);
_frameStreams?.ResponseBody.Abort();
try
{
ConnectionControl.End(ProduceEndType.SocketDisconnect);
}
catch (Exception ex)
{
Log.LogError(0, ex, "Abort");
}
LifetimeControl.End(ProduceEndType.SocketDisconnect);
try
{
RequestAbortedSource.Cancel();
}
catch (Exception ex)
{
Log.LogError(0, ex, "Abort");
}
_abortedCts = null;
// Potentially calling user code. CancelRequestAbortedToken logs any exceptions.
ServiceContext.ThreadPool.UnsafeRun(state => ((Frame)state).CancelRequestAbortedToken(), this);
}
}
@ -856,7 +854,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (_keepAlive)
{
ConnectionControl.End(ProduceEndType.ConnectionKeepAlive);
LifetimeControl.End(ProduceEndType.ConnectionKeepAlive);
}
if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0)
@ -876,7 +874,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (_keepAlive)
{
ConnectionControl.End(ProduceEndType.ConnectionKeepAlive);
LifetimeControl.End(ProduceEndType.ConnectionKeepAlive);
}
}
@ -995,7 +993,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
break;
}
ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
TimeoutControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
goto case RequestProcessingStatus.ParsingRequestLine;
@ -1060,7 +1058,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
if (result)
{
ConnectionControl.CancelTimeout();
TimeoutControl.CancelTimeout();
}
return result;
}

View File

@ -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 Microsoft.AspNetCore.Server.Kestrel.Transport;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class FrameContext
{
public string ConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; }
public IConnectionInformation ConnectionInformation { get; set; }
}
}

View File

@ -6,8 +6,7 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Exceptions;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
@ -16,8 +15,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
private readonly IHttpApplication<TContext> _application;
public Frame(IHttpApplication<TContext> application, ConnectionContext context)
: base(context)
public Frame(IHttpApplication<TContext> application, FrameContext frameContext)
: base(frameContext)
{
_application = application;
}
@ -34,13 +33,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
while (!_requestProcessingStopping)
{
ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
TimeoutControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
InitializeHeaders();
while (!_requestProcessingStopping)
{
var result = await Input.Reader.ReadAsync();
var result = await Input.ReadAsync();
var examined = result.Buffer.End;
var consumed = result.Buffer.End;
@ -52,13 +51,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders)
{
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
throw BadHttpRequestException.GetException(RequestRejectionReason
.MalformedRequestInvalidHeaders);
}
throw;
}
finally
{
Input.Reader.Advance(consumed, examined);
Input.Advance(consumed, examined);
}
if (_requestProcessingStatus == RequestProcessingStatus.AppStarted)
@ -73,9 +73,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
case RequestProcessingStatus.RequestPending:
return;
case RequestProcessingStatus.ParsingRequestLine:
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
throw BadHttpRequestException.GetException(
RequestRejectionReason.InvalidRequestLine);
case RequestProcessingStatus.ParsingHeaders:
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
throw BadHttpRequestException.GetException(RequestRejectionReason
.MalformedRequestInvalidHeaders);
}
}
}
@ -190,15 +192,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// SetBadRequestState logs the error.
SetBadRequestState(ex);
}
catch (IOException ex) when (ex.InnerException is UvException)
catch (ConnectionResetException ex)
{
// Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly.
if (_requestProcessingStatus != RequestProcessingStatus.RequestPending ||
((UvException)ex.InnerException).StatusCode != Constants.ECONNRESET)
if (_requestProcessingStatus != RequestProcessingStatus.RequestPending)
{
Log.RequestProcessingError(ConnectionId, ex);
}
}
catch (IOException ex)
{
Log.RequestProcessingError(ConnectionId, ex);
}
catch (Exception ex)
{
Log.LogWarning(0, ex, "Connection processing ended abnormally");
@ -207,12 +212,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
try
{
Input.Reader.Complete();
Input.Complete();
// If _requestAborted is set, the connection has already been closed.
if (Volatile.Read(ref _requestAborted) == 0)
{
await TryProduceInvalidRequestResponse();
ConnectionControl.End(ProduceEndType.SocketShutdown);
LifetimeControl.End(ProduceEndType.SocketShutdown);
}
}
catch (Exception ex)

View File

@ -5,7 +5,6 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http

View File

@ -6,7 +6,6 @@ using System.Collections;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;

View File

@ -5,7 +5,6 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{

View File

@ -2,9 +2,9 @@
// 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 System.Threading.Tasks;
using System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{

View File

@ -3,11 +3,8 @@
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public interface IConnectionControl
public interface ITimeoutControl
{
void Pause();
void Resume();
void End(ProduceEndType endType);
void SetTimeout(long milliseconds, TimeoutAction timeoutAction);
void ResetTimeout(long milliseconds, TimeoutAction timeoutAction);
void CancelTimeout();

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.CompilerServices;

View File

@ -215,9 +215,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private void ConsumedBytes(int count)
{
var scan = _context.Input.Reader.ReadAsync().GetResult().Buffer;
var scan = _context.Input.ReadAsync().GetResult().Buffer;
var consumed = scan.Move(scan.Start, count);
_context.Input.Reader.Advance(consumed, consumed);
_context.Input.Advance(consumed, consumed);
OnConsumedBytes(count);
}
@ -304,7 +304,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
protected override ValueTask<ArraySegment<byte>> PeekAsync(CancellationToken cancellationToken)
{
return _context.Input.Reader.PeekAsync();
return _context.Input.PeekAsync();
}
}
@ -351,7 +351,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return new ValueTask<ArraySegment<byte>>();
}
var task = _context.Input.Reader.PeekAsync();
var task = _context.Input.PeekAsync();
if (task.IsCompleted)
{
@ -423,7 +423,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
: base(context)
{
RequestKeepAlive = keepAlive;
_input = _context.Input.Reader;
_input = _context.Input;
_requestHeaders = headers;
}

View File

@ -6,18 +6,14 @@ using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class SocketOutput : ISocketOutput
public class SocketOutputProducer : ISocketOutput, IDisposable
{
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
private readonly KestrelThread _thread;
private readonly UvStreamHandle _socket;
private readonly Connection _connection;
private readonly string _connectionId;
private readonly IKestrelTrace _log;
@ -26,10 +22,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private bool _cancelled = false;
private bool _completed = false;
private Exception _lastWriteError;
private readonly WriteReqPool _writeReqPool;
private readonly IPipe _pipe;
private Task _writingTask;
private readonly IPipeWriter _pipe;
private readonly Frame _frame;
// https://github.com/dotnet/corefxlab/issues/1334
// Pipelines don't support multiple awaiters on flush
@ -38,24 +33,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private readonly object _flushLock = new object();
private readonly Action _onFlushCallback;
public SocketOutput(
IPipe pipe,
KestrelThread thread,
UvStreamHandle socket,
Connection connection,
string connectionId,
IKestrelTrace log)
public SocketOutputProducer(IPipeWriter pipe, Frame frame, string connectionId, IKestrelTrace log)
{
_pipe = pipe;
// We need to have empty pipe at this moment so callback
// get's scheduled
_writingTask = StartWrites();
_thread = thread;
_socket = socket;
_connection = connection;
_frame = frame;
_connectionId = connectionId;
_log = log;
_writeReqPool = thread.WriteReqPool;
_onFlushCallback = OnFlush;
}
@ -68,19 +51,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
lock (_contextLock)
{
if (_socket.IsClosed)
{
_log.ConnectionDisconnectedWrite(_connectionId, buffer.Count, _lastWriteError);
return TaskCache.CompletedTask;
}
if (_completed)
{
// TODO: Get actual notification when the consumer stopped from Pipes,
// so we know if the socket is fully closed and why (for logging exceptions);
_log.ConnectionDisconnectedWrite(_connectionId, buffer.Count, ex: null);
return TaskCache.CompletedTask;
}
writableBuffer = _pipe.Writer.Alloc();
writableBuffer = _pipe.Alloc();
if (buffer.Count > 0)
{
@ -103,23 +82,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return FlushAsync(writableBuffer);
}
public void End(ProduceEndType endType)
{
if (endType == ProduceEndType.SocketShutdown)
{
// Graceful shutdown
_pipe.Reader.CancelPendingRead();
}
lock (_contextLock)
{
_completed = true;
}
// We're done writing
_pipe.Writer.Complete();
}
private Task FlushAsync(WritableBuffer writableBuffer)
{
var awaitable = writableBuffer.FlushAsync();
@ -164,7 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
if (cancellationToken.IsCancellationRequested)
{
_connection.AbortAsync();
_frame.Abort();
_cancelled = true;
return Task.FromCanceled(cancellationToken);
}
@ -186,7 +148,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return WriteAsync(_emptyData, cancellationToken);
}
public void Write<T>(Action<WritableBuffer, T> callback, T state)
void ISocketOutput.Write<T>(Action<WritableBuffer, T> callback, T state)
{
lock (_contextLock)
{
@ -195,106 +157,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return;
}
var buffer = _pipe.Writer.Alloc();
var buffer = _pipe.Alloc();
callback(buffer, state);
buffer.Commit();
}
}
public async Task StartWrites()
public void Dispose()
{
while (true)
lock (_contextLock)
{
var result = await _pipe.Reader.ReadAsync();
var buffer = result.Buffer;
try
{
if (!buffer.IsEmpty)
{
var writeReq = _writeReqPool.Allocate();
var writeResult = await writeReq.WriteAsync(_socket, buffer);
_writeReqPool.Return(writeReq);
// REVIEW: Locking here, do we need to take the context lock?
OnWriteCompleted(writeResult.Status, writeResult.Error);
}
if (result.IsCancelled)
{
// Send a FIN
await ShutdownAsync();
}
if (buffer.IsEmpty && result.IsCompleted)
{
break;
}
}
finally
{
_pipe.Reader.Advance(result.Buffer.End);
}
_completed = true;
_pipe.Complete();
}
// We're done reading
_pipe.Reader.Complete();
_socket.Dispose();
_connection.OnSocketClosed();
_log.ConnectionStop(_connectionId);
}
private void OnWriteCompleted(int writeStatus, Exception writeError)
{
// Called inside _contextLock
var status = writeStatus;
var error = writeError;
if (error != null)
{
// Abort the connection for any failed write
// Queued on threadpool so get it in as first op.
_connection.AbortAsync();
_cancelled = true;
_lastWriteError = error;
}
if (error == null)
{
_log.ConnectionWriteCallback(_connectionId, status);
}
else
{
// Log connection resets at a lower (Debug) level.
if (status == Constants.ECONNRESET)
{
_log.ConnectionReset(_connectionId);
}
else
{
_log.ConnectionError(_connectionId, error);
}
}
}
private Task ShutdownAsync()
{
var tcs = new TaskCompletionSource<object>();
_log.ConnectionWriteFin(_connectionId);
var shutdownReq = new UvShutdownReq(_log);
shutdownReq.Init(_thread.Loop);
shutdownReq.Shutdown(_socket, (req, status, state) =>
{
req.Dispose();
_log.ConnectionWroteFin(_connectionId, status);
tcs.TrySetResult(null);
},
this);
return tcs.Task;
}
}
}

View File

@ -0,0 +1,32 @@
// 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.Server.Kestrel.Internal.Infrastructure
{
internal static class Constants
{
public const int MaxExceptionDetailSize = 128;
/// <summary>
/// The IPEndPoint Kestrel will bind to if nothing else is specified.
/// </summary>
public static readonly string DefaultServerAddress = "http://localhost:5000";
/// <summary>
/// Prefix of host name used to specify Unix sockets in the configuration.
/// </summary>
public const string UnixPipeHostPrefix = "unix:/";
/// <summary>
/// Prefix of host name used to specify pipe file descriptor in the configuration.
/// </summary>
public const string PipeDescriptorPrefix = "pipefd:";
/// <summary>
/// Prefix of host name used to specify socket descriptor in the configuration.
/// </summary>
public const string SocketDescriptorPrefix = "sockfd:";
public const string ServerName = "Kestrel";
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
@ -10,16 +9,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
{
public class ServiceContext
{
public IApplicationLifetime AppLifetime { get; set; }
public IKestrelTrace Log { get; set; }
public IThreadPool ThreadPool { get; set; }
public Func<Frame, IHttpParser> HttpParserFactory { get; set; }
public Func<ConnectionContext, Frame> FrameFactory { get; set; }
public DateHeaderValueManager DateHeaderValueManager { get; set; }
public KestrelServerOptions ServerOptions { get; set; }

View File

@ -7,15 +7,16 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Exceptions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -23,21 +24,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel
{
public class KestrelServer : IServer
{
private Stack<IDisposable> _disposables;
private readonly IApplicationLifetime _applicationLifetime;
//private Stack<IDisposable> _disposables;
private readonly List<ITransport> _transports = new List<ITransport>();
private readonly ILogger _logger;
private readonly IServerAddressesFeature _serverAddresses;
private readonly ITransportFactory _transportFactory;
public KestrelServer(IOptions<KestrelServerOptions> options, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory)
private bool _isRunning;
private DateHeaderValueManager _dateHeaderValueManager;
public KestrelServer(
IOptions<KestrelServerOptions> options,
ITransportFactory transportFactory,
ILoggerFactory loggerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (applicationLifetime == null)
if (transportFactory == null)
{
throw new ArgumentNullException(nameof(applicationLifetime));
throw new ArgumentNullException(nameof(transportFactory));
}
if (loggerFactory == null)
@ -47,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
Options = options.Value ?? new KestrelServerOptions();
InternalOptions = new InternalKestrelServerOptions();
_applicationLifetime = applicationLifetime;
_transportFactory = transportFactory;
_logger = loggerFactory.CreateLogger(typeof(KestrelServer).GetTypeInfo().Namespace);
Features = new FeatureCollection();
_serverAddresses = new ServerAddressesFeature();
@ -72,14 +81,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel
ValidateOptions();
if (_disposables != null)
if (_isRunning)
{
// The server has already started and/or has not been cleaned up yet
throw new InvalidOperationException("Server has already started.");
}
_disposables = new Stack<IDisposable>();
_isRunning = true;
var dateHeaderValueManager = new DateHeaderValueManager();
_dateHeaderValueManager = new DateHeaderValueManager();
var trace = new KestrelTrace(_logger);
IThreadPool threadPool;
@ -92,43 +101,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel
threadPool = new InlineLoggingThreadPool(trace);
}
var engine = new KestrelEngine(new ServiceContext
var serviceContext = new ServiceContext
{
FrameFactory = context =>
{
return new Frame<TContext>(application, context);
},
AppLifetime = _applicationLifetime,
Log = trace,
HttpParserFactory = frame => new KestrelHttpParser(frame.ConnectionContext.ListenerContext.ServiceContext.Log),
HttpParserFactory = frame => new KestrelHttpParser(frame.ServiceContext.Log),
ThreadPool = threadPool,
DateHeaderValueManager = dateHeaderValueManager,
DateHeaderValueManager = _dateHeaderValueManager,
ServerOptions = Options
});
};
_disposables.Push(engine);
_disposables.Push(dateHeaderValueManager);
var threadCount = Options.ThreadCount;
if (threadCount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(threadCount),
threadCount,
"ThreadCount must be positive.");
}
if (!Constants.ECONNRESET.HasValue)
{
_logger.LogWarning("Unable to determine ECONNRESET value on this platform.");
}
if (!Constants.EADDRINUSE.HasValue)
{
_logger.LogWarning("Unable to determine EADDRINUSE value on this platform.");
}
engine.Start(threadCount);
var connectionHandler = new ConnectionHandler<TContext>(serviceContext, application);
var listenOptions = Options.ListenOptions;
var hasListenOptions = listenOptions.Any();
@ -137,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
if (hasListenOptions && hasServerAddresses)
{
var joined = string.Join(", ", _serverAddresses.Addresses);
_logger.LogWarning($"Overriding address(es) '{joined}'. Binding to endpoints defined in {nameof(WebHostBuilderKestrelExtensions.UseKestrel)}() instead.");
_logger.LogWarning($"Overriding address(es) '{joined}'. Binding to endpoints defined in UseKestrel() instead.");
_serverAddresses.Addresses.Clear();
}
@ -146,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
_logger.LogDebug($"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.");
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
StartLocalhost(engine, ServerAddress.FromUrl(Constants.DefaultServerAddress));
StartLocalhost(connectionHandler, ServerAddress.FromUrl(Constants.DefaultServerAddress));
// If StartLocalhost doesn't throw, there is at least one listener.
// The port cannot change for "localhost".
@ -181,7 +163,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
{
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
StartLocalhost(engine, parsedAddress);
StartLocalhost(connectionHandler, parsedAddress);
// If StartLocalhost doesn't throw, there is at least one listener.
// The port cannot change for "localhost".
@ -201,18 +183,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel
foreach (var endPoint in listenOptions)
{
var transport = _transportFactory.Create(endPoint, connectionHandler);
_transports.Add(transport);
try
{
_disposables.Push(engine.CreateServer(endPoint));
transport.BindAsync().Wait();
}
catch (AggregateException ex)
catch (AggregateException ex) when (ex.InnerException is AddressInUseException)
{
if ((ex.InnerException as UvException)?.StatusCode == Constants.EADDRINUSE)
{
throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex);
}
throw;
throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex);
}
// If requested port was "0", replace with assigned dynamic port.
@ -229,14 +209,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel
public void Dispose()
{
if (_disposables != null)
if (_transports != null)
{
while (_disposables.Count > 0)
var tasks = new Task[_transports.Count];
for (int i = 0; i < _transports.Count; i++)
{
_disposables.Pop().Dispose();
tasks[i] = _transports[i].UnbindAsync();
}
_disposables = null;
Task.WaitAll(tasks);
// TODO: Do transport-agnostic connection management/shutdown.
for (int i = 0; i < _transports.Count; i++)
{
tasks[i] = _transports[i].StopAsync();
}
Task.WaitAll(tasks);
}
_dateHeaderValueManager?.Dispose();
}
private void ValidateOptions()
@ -256,7 +246,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
}
}
private void StartLocalhost(KestrelEngine engine, ServerAddress parsedAddress)
private void StartLocalhost<TContext>(ConnectionHandler<TContext> connectionHandler, ServerAddress parsedAddress)
{
if (parsedAddress.Port == 0)
{
@ -272,20 +262,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel
Scheme = parsedAddress.Scheme,
};
_disposables.Push(engine.CreateServer(ipv4ListenOptions));
var transport = _transportFactory.Create(ipv4ListenOptions, connectionHandler);
_transports.Add(transport);
transport.BindAsync().Wait();
}
catch (AggregateException ex) when (ex.InnerException is UvException)
catch (AggregateException ex) when (ex.InnerException is AddressInUseException)
{
var uvEx = (UvException)ex.InnerException;
if (uvEx.StatusCode == Constants.EADDRINUSE)
{
throw new IOException($"Failed to bind to address {parsedAddress} on the IPv4 loopback interface: port already in use.", ex);
}
else
{
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv4 loopback interface: ({uvEx.Message})");
exceptions.Add(uvEx);
}
}
catch (AggregateException ex)
{
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv4 loopback interface: ({ex.Message})");
exceptions.Add(ex.InnerException);
}
try
@ -295,20 +283,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel
Scheme = parsedAddress.Scheme,
};
_disposables.Push(engine.CreateServer(ipv6ListenOptions));
var transport = _transportFactory.Create(ipv6ListenOptions, connectionHandler);
_transports.Add(transport);
transport.BindAsync().Wait();
}
catch (AggregateException ex) when (ex.InnerException is UvException)
catch (AggregateException ex) when (ex.InnerException is AddressInUseException)
{
var uvEx = (UvException)ex.InnerException;
if (uvEx.StatusCode == Constants.EADDRINUSE)
{
throw new IOException($"Failed to bind to address {parsedAddress} on the IPv6 loopback interface: port already in use.", ex);
}
else
{
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv6 loopback interface: ({uvEx.Message})");
exceptions.Add(uvEx);
}
throw new IOException($"Failed to bind to address {parsedAddress} on the IPv6 loopback interface: port already in use.", ex);
}
catch (AggregateException ex)
{
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv6 loopback interface: ({ex.Message})");
exceptions.Add(ex.InnerException);
}
if (exceptions.Count == 2)

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
/// <summary>
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
/// Typically initialized by <see cref="Hosting.WebHostBuilderKestrelExtensions.UseKestrel(Hosting.IWebHostBuilder, Action{KestrelServerOptions})"/>.
/// Typically initialized by UseKestrel()"/>.
/// </summary>
public IServiceProvider ApplicationServices { get; set; }
@ -65,50 +65,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
/// </summary>
public KestrelServerLimits Limits { get; } = new KestrelServerLimits();
/// <summary>
/// The amount of time after the server begins shutting down before connections will be forcefully closed.
/// Kestrel will wait for the duration of the timeout for any ongoing request processing to complete before
/// terminating the connection. No new connections or requests will be accepted during this time.
/// </summary>
/// <remarks>
/// Defaults to 5 seconds.
/// </remarks>
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// The number of libuv I/O threads used to process requests.
/// </summary>
/// <remarks>
/// Defaults to half of <see cref="Environment.ProcessorCount" /> rounded down and clamped between 1 and 16.
/// </remarks>
public int ThreadCount { get; set; } = ProcessorThreadCount;
private static int ProcessorThreadCount
{
get
{
// Actual core count would be a better number
// rather than logical cores which includes hyper-threaded cores.
// Divide by 2 for hyper-threading, and good defaults (still need threads to do webserving).
var threadCount = Environment.ProcessorCount >> 1;
if (threadCount < 1)
{
// Ensure shifted value is at least one
return 1;
}
if (threadCount > 16)
{
// Receive Side Scaling RSS Processor count currently maxes out at 16
// would be better to check the NIC's current hardware queues; but xplat...
return 16;
}
return threadCount;
}
}
/// <summary>
/// Bind to given IP address and port.
/// </summary>

View File

@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
/// The <see cref="IPEndPoint"/> to bind to.
/// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.IPEndPoint"/>.
/// </summary>
public IPEndPoint IPEndPoint { get; internal set; }
public IPEndPoint IPEndPoint { get; set; }
/// <summary>
/// The absolute path to a Unix domain socket to bind to.
@ -82,7 +82,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
public List<IConnectionAdapter> ConnectionAdapters { get; } = new List<IConnectionAdapter>();
// Scheme is hopefully only a temporary measure for back compat with IServerAddressesFeature.
internal string Scheme { get; set; } = "http";
// TODO: Allow connection adapters to configure the scheme
public string Scheme { get; set; } = "http";
public override string ToString()
{

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<Description>Core components of ASP.NET Core Kestrel cross-platform web server.</Description>
<TargetFrameworks>netstandard1.3;net46</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;kestrel</PackageTags>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>CS1591;$(NoWarn)</NoWarn>
<EnableApiCheck>false</EnableApiCheck>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Libuv" Version="$(LibUvVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
<PackageReference Include="System.Numerics.Vectors" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(CoreFxVersion)" />
<PackageReference Include="System.IO.Pipelines" Version="$(CoreFxLabsPipelinesVersion)" />
<PackageReference Include="System.Text.Encodings.Web.Utf8" Version="$(CoreFxLabsVersion)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Diagnostics.Process" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Threading.Thread" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Threading.ThreadPool" Version="$(CoreFxVersion)" />
</ItemGroup>
</Project>

View File

@ -1,8 +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.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,18 @@
// 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.Server.Kestrel.Transport.Exceptions
{
public class AddressInUseException : InvalidOperationException
{
public AddressInUseException(string message) : base(message)
{
}
public AddressInUseException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Exceptions
{
public class ConnectionResetException : IOException
{
public ConnectionResetException(string message) : base(message)
{
}
public ConnectionResetException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO.Pipelines;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport
{
public interface IConnectionContext
{
string ConnectionId { get; }
IPipeWriter Input { get; }
IPipeReader Output { get; }
// TODO: Remove these (Use Pipes instead?)
Task StopAsync();
void Abort(Exception ex);
void Timeout();
}
}

View File

@ -0,0 +1,10 @@
// 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.Server.Kestrel.Transport
{
public interface IConnectionHandler
{
IConnectionContext OnConnection(IConnectionInformation connectionInfo);
}
}

View File

@ -0,0 +1,23 @@
// 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.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport
{
public interface IConnectionInformation
{
ListenOptions ListenOptions { get; }
IPEndPoint RemoteEndPoint { get; }
IPEndPoint LocalEndPoint { get; }
PipeFactory PipeFactory { get; }
IScheduler InputWriterScheduler { get; }
IScheduler OutputWriterScheduler { get; }
// TODO: Remove timeout management from transport
ITimeoutControl TimeoutControl { get; }
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport
{
public interface ITransport
{
// Can only be called once per ITransport
Task BindAsync();
Task UnbindAsync();
Task StopAsync();
}
}

View File

@ -0,0 +1,10 @@
// 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.Server.Kestrel.Transport
{
public interface ITransportFactory
{
ITransport Create(ListenOptions listenOptions, IConnectionHandler handler);
}
}

View File

@ -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;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Server.Kestrel;

View File

@ -8,10 +8,11 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;kestrel</PackageTags>
<NoWarn>CS1591;$(NoWarn)</NoWarn>
<EnableApiCheck>false</EnableApiCheck>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Server.Kestrel\Microsoft.AspNetCore.Server.Kestrel.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Server.Kestrel.Core\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,292 +0,0 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Https, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Hosting.KestrelServerOptionsHttpsExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "fileName",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "fileName",
"Type": "System.String"
},
{
"Name": "password",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "serverCertificate",
"Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "httpsOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode",
"Visibility": "Public",
"Kind": "Enumeration",
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Field",
"Name": "NoCertificate",
"Parameters": [],
"GenericParameter": [],
"Literal": "0"
},
{
"Kind": "Field",
"Name": "AllowCertificate",
"Parameters": [],
"GenericParameter": [],
"Literal": "1"
},
{
"Kind": "Field",
"Name": "RequireCertificate",
"Parameters": [],
"GenericParameter": [],
"Literal": "2"
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilter",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter"
],
"Members": [
{
"Kind": "Method",
"Name": "OnConnectionAsync",
"Parameters": [
{
"Name": "context",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Filter.ConnectionFilterContext"
}
],
"ReturnType": "System.Threading.Tasks.Task",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions"
},
{
"Name": "previous",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_ServerCertificate",
"Parameters": [],
"ReturnType": "System.Security.Cryptography.X509Certificates.X509Certificate2",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ServerCertificate",
"Parameters": [
{
"Name": "value",
"Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ClientCertificateMode",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ClientCertificateMode",
"Parameters": [
{
"Name": "value",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ClientCertificateValidation",
"Parameters": [],
"ReturnType": "System.Func<System.Security.Cryptography.X509Certificates.X509Certificate2, System.Security.Cryptography.X509Certificates.X509Chain, System.Net.Security.SslPolicyErrors, System.Boolean>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ClientCertificateValidation",
"Parameters": [
{
"Name": "value",
"Type": "System.Func<System.Security.Cryptography.X509Certificates.X509Certificate2, System.Security.Cryptography.X509Certificates.X509Chain, System.Net.Security.SslPolicyErrors, System.Boolean>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_SslProtocols",
"Parameters": [],
"ReturnType": "System.Security.Authentication.SslProtocols",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_SslProtocols",
"Parameters": [
{
"Name": "value",
"Type": "System.Security.Authentication.SslProtocols"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_CheckCertificateRevocation",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_CheckCertificateRevocation",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -1,292 +0,0 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Server.Kestrel.Https, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Hosting.KestrelServerOptionsHttpsExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "fileName",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "fileName",
"Type": "System.String"
},
{
"Name": "password",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "serverCertificate",
"Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "UseHttps",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions"
},
{
"Name": "httpsOptions",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions"
}
],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode",
"Visibility": "Public",
"Kind": "Enumeration",
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Field",
"Name": "NoCertificate",
"Parameters": [],
"GenericParameter": [],
"Literal": "0"
},
{
"Kind": "Field",
"Name": "AllowCertificate",
"Parameters": [],
"GenericParameter": [],
"Literal": "1"
},
{
"Kind": "Field",
"Name": "RequireCertificate",
"Parameters": [],
"GenericParameter": [],
"Literal": "2"
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilter",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter"
],
"Members": [
{
"Kind": "Method",
"Name": "OnConnectionAsync",
"Parameters": [
{
"Name": "context",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Filter.ConnectionFilterContext"
}
],
"ReturnType": "System.Threading.Tasks.Task",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "options",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions"
},
{
"Name": "previous",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_ServerCertificate",
"Parameters": [],
"ReturnType": "System.Security.Cryptography.X509Certificates.X509Certificate2",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ServerCertificate",
"Parameters": [
{
"Name": "value",
"Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ClientCertificateMode",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ClientCertificateMode",
"Parameters": [
{
"Name": "value",
"Type": "Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ClientCertificateValidation",
"Parameters": [],
"ReturnType": "System.Func<System.Security.Cryptography.X509Certificates.X509Certificate2, System.Security.Cryptography.X509Certificates.X509Chain, System.Net.Security.SslPolicyErrors, System.Boolean>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ClientCertificateValidation",
"Parameters": [
{
"Name": "value",
"Type": "System.Func<System.Security.Cryptography.X509Certificates.X509Certificate2, System.Security.Cryptography.X509Certificates.X509Chain, System.Net.Security.SslPolicyErrors, System.Boolean>"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_SslProtocols",
"Parameters": [],
"ReturnType": "System.Security.Authentication.SslProtocols",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_SslProtocols",
"Parameters": [
{
"Name": "value",
"Type": "System.Security.Authentication.SslProtocols"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_CheckCertificateRevocation",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_CheckCertificateRevocation",
"Parameters": [
{
"Name": "value",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -1,14 +0,0 @@
[
{
"OldTypeId": "public static class Microsoft.AspNetCore.Hosting.KestrelServerOptionsHttpsExtensions",
"Kind": "Removal"
},
{
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
"Kind": "Removal"
},
{
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions",
"Kind": "Removal"
}
]

View File

@ -1,14 +0,0 @@
[
{
"OldTypeId": "public static class Microsoft.AspNetCore.Hosting.KestrelServerOptionsHttpsExtensions",
"Kind": "Removal"
},
{
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
"Kind": "Removal"
},
{
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions",
"Kind": "Removal"
}
]

View File

@ -0,0 +1,276 @@
// 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.Diagnostics;
using System.IO;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Exceptions;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class Connection : ConnectionContext, ITimeoutControl
{
private const int MinAllocBufferSize = 2048;
private static readonly Action<UvStreamHandle, int, object> _readCallback =
(handle, status, state) => ReadCallback(handle, status, state);
private static readonly Func<UvStreamHandle, int, object, LibuvFunctions.uv_buf_t> _allocCallback =
(handle, suggestedsize, state) => AllocCallback(handle, suggestedsize, state);
private readonly UvStreamHandle _socket;
private IConnectionContext _connectionContext;
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue;
private TimeoutAction _timeoutAction;
private WritableBuffer? _currentWritableBuffer;
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
{
_socket = socket;
socket.Connection = this;
TimeoutControl = this;
var tcpHandle = _socket as UvTcpHandle;
if (tcpHandle != null)
{
RemoteEndPoint = tcpHandle.GetPeerIPEndPoint();
LocalEndPoint = tcpHandle.GetSockIPEndPoint();
}
_lastTimestamp = Thread.Loop.Now();
}
// For testing
public Connection()
{
}
public string ConnectionId { get; set; }
public IPipeWriter Input { get; set; }
public SocketOutputConsumer Output { get; set; }
private IKestrelTrace Log => ListenerContext.TransportContext.Log;
private IConnectionHandler ConnectionHandler => ListenerContext.TransportContext.ConnectionHandler;
private KestrelThread Thread => ListenerContext.Thread;
public void Start()
{
try
{
_connectionContext = ConnectionHandler.OnConnection(this);
ConnectionId = _connectionContext.ConnectionId;
Log.ConnectionStart(ConnectionId);
KestrelEventSource.Log.ConnectionStart(this);
Input = _connectionContext.Input;
Output = new SocketOutputConsumer(_connectionContext.Output, Thread, _socket, this, ConnectionId, Log);
// Start socket prior to applying the ConnectionAdapter
_socket.ReadStart(_allocCallback, _readCallback, this);
_lastTimestamp = Thread.Loop.Now();
}
catch (Exception e)
{
Log.LogError(0, e, "Connection.StartFrame");
throw;
}
}
public Task StopAsync()
{
return Task.WhenAll(_connectionContext.StopAsync(), _socketClosedTcs.Task);
}
public virtual Task AbortAsync(Exception error = null)
{
_connectionContext.Abort(error);
return _socketClosedTcs.Task;
}
// Called on Libuv thread
public virtual void OnSocketClosed()
{
KestrelEventSource.Log.ConnectionStop(this);
Input.Complete(new TaskCanceledException("The request was aborted"));
_socketClosedTcs.TrySetResult(null);
}
// Called on Libuv thread
public void Tick(long timestamp)
{
if (timestamp > PlatformApis.VolatileRead(ref _timeoutTimestamp))
{
TimeoutControl.CancelTimeout();
if (_timeoutAction == TimeoutAction.SendTimeoutResponse)
{
_connectionContext.Timeout();
}
StopAsync();
}
Interlocked.Exchange(ref _lastTimestamp, timestamp);
}
private static LibuvFunctions.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state)
{
return ((Connection)state).OnAlloc(handle, suggestedSize);
}
private unsafe LibuvFunctions.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize)
{
Debug.Assert(_currentWritableBuffer == null);
var currentWritableBuffer = Input.Alloc(MinAllocBufferSize);
_currentWritableBuffer = currentWritableBuffer;
void* dataPtr;
var tryGetPointer = currentWritableBuffer.Buffer.TryGetPointer(out dataPtr);
Debug.Assert(tryGetPointer);
return handle.Libuv.buf_init(
(IntPtr)dataPtr,
currentWritableBuffer.Buffer.Length);
}
private static void ReadCallback(UvStreamHandle handle, int status, object state)
{
((Connection)state).OnRead(handle, status);
}
private async void OnRead(UvStreamHandle handle, int status)
{
var normalRead = status >= 0;
var normalDone = status == Constants.EOF;
var errorDone = !(normalDone || normalRead);
var readCount = normalRead ? status : 0;
if (normalRead)
{
Log.ConnectionRead(ConnectionId, readCount);
}
else
{
_socket.ReadStop();
if (normalDone)
{
Log.ConnectionReadFin(ConnectionId);
}
}
IOException error = null;
WritableBufferAwaitable? flushTask = null;
if (errorDone)
{
Exception uvError;
handle.Libuv.Check(status, out uvError);
// Log connection resets at a lower (Debug) level.
if (status == Constants.ECONNRESET)
{
Log.ConnectionReset(ConnectionId);
error = new ConnectionResetException(uvError.Message, uvError);
}
else
{
Log.ConnectionError(ConnectionId, uvError);
error = new IOException(uvError.Message, uvError);
}
_currentWritableBuffer?.Commit();
}
else
{
Debug.Assert(_currentWritableBuffer != null);
var currentWritableBuffer = _currentWritableBuffer.Value;
currentWritableBuffer.Advance(readCount);
flushTask = currentWritableBuffer.FlushAsync();
}
_currentWritableBuffer = null;
if (flushTask?.IsCompleted == false)
{
Pause();
var result = await flushTask.Value;
// If the reader isn't complete then resume
if (!result.IsCompleted)
{
Resume();
}
}
if (!normalRead)
{
Input.Complete(error);
var ignore = AbortAsync(error);
}
}
private void Pause()
{
// It's possible that uv_close was called between the call to Thread.Post() and now.
if (!_socket.IsClosed)
{
_socket.ReadStop();
}
}
private void Resume()
{
// It's possible that uv_close was called even before the call to Resume().
if (!_socket.IsClosed)
{
try
{
_socket.ReadStart(_allocCallback, _readCallback, this);
}
catch (UvException)
{
// ReadStart() can throw a UvException in some cases (e.g. socket is no longer connected).
// This should be treated the same as OnRead() seeing a "normalDone" condition.
Log.ConnectionReadFin(ConnectionId);
Input.Complete();
}
}
}
void ITimeoutControl.SetTimeout(long milliseconds, TimeoutAction timeoutAction)
{
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
AssignTimeout(milliseconds, timeoutAction);
}
void ITimeoutControl.ResetTimeout(long milliseconds, TimeoutAction timeoutAction)
{
AssignTimeout(milliseconds, timeoutAction);
}
void ITimeoutControl.CancelTimeout()
{
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
}
private void AssignTimeout(long milliseconds, TimeoutAction timeoutAction)
{
_timeoutAction = timeoutAction;
// Add KestrelThread.HeartbeatMilliseconds extra milliseconds since this can be called right before the next heartbeat.
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + milliseconds + KestrelThread.HeartbeatMilliseconds);
}
}
}

View File

@ -1,14 +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.Net;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Http.Features;
using System.Net;
using Microsoft.AspNetCore.Server.Kestrel.Transport;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class ConnectionContext
public class ConnectionContext : IConnectionInformation
{
public ConnectionContext()
{
@ -21,16 +20,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public ListenerContext ListenerContext { get; set; }
public IPipe Input { get; set; }
public ISocketOutput Output { get; set; }
public IConnectionControl ConnectionControl { get; set; }
public ListenOptions ListenOptions => ListenerContext.ListenOptions;
public IPEndPoint RemoteEndPoint { get; set; }
public IPEndPoint LocalEndPoint { get; set; }
public string ConnectionId { get; set; }
public PipeFactory PipeFactory => ListenerContext.Thread.PipelineFactory;
public IScheduler InputWriterScheduler => ListenerContext.Thread;
public IScheduler OutputWriterScheduler => ListenerContext.Thread;
public ITimeoutControl TimeoutControl { get; set; }
}
}

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
@ -12,12 +11,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public class ConnectionManager
{
private readonly KestrelThread _thread;
private readonly IThreadPool _threadPool;
public ConnectionManager(KestrelThread thread, IThreadPool threadPool)
public ConnectionManager(KestrelThread thread)
{
_thread = thread;
_threadPool = threadPool;
}
public async Task<bool> WalkConnectionsAndCloseAsync(TimeSpan timeout)
@ -64,9 +61,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
});
_threadPool.Run(() =>
Task.Run(() =>
{
Task.WaitAll(tasks.ToArray());
try
{
Task.WaitAll(tasks.ToArray());
}
catch (Exception ex)
{
tcs.SetException(ex);
return;
}
tcs.SetResult(null);
});
}

View File

@ -5,6 +5,7 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
@ -16,14 +17,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
private bool _closed;
public Listener(ServiceContext serviceContext)
: base(serviceContext)
public Listener(LibuvTransportContext transportContext) : base(transportContext)
{
}
protected UvStreamHandle ListenSocket { get; private set; }
public IKestrelTrace Log => ServiceContext.Log;
public IKestrelTrace Log => TransportContext.Log;
public Task StartAsync(
ListenOptions listenOptions,

View File

@ -0,0 +1,45 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class ListenerContext
{
public ListenerContext(LibuvTransportContext transportContext)
{
TransportContext = transportContext;
}
public LibuvTransportContext TransportContext { get; set; }
public ListenOptions ListenOptions { get; set; }
public KestrelThread Thread { get; set; }
/// <summary>
/// Creates a socket which can be used to accept an incoming connection.
/// </summary>
protected UvStreamHandle CreateAcceptSocket()
{
switch (ListenOptions.Type)
{
case ListenType.IPEndPoint:
case ListenType.FileHandle:
var tcpHandle = new UvTcpHandle(TransportContext.Log);
tcpHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
tcpHandle.NoDelay(ListenOptions.NoDelay);
return tcpHandle;
case ListenType.SocketPath:
var pipeHandle = new UvPipeHandle(TransportContext.Log);
pipeHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
return pipeHandle;
default:
throw new InvalidOperationException();
}
}
}
}

View File

@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// but it has no other functional significance
private readonly ArraySegment<ArraySegment<byte>> _dummyMessage = new ArraySegment<ArraySegment<byte>>(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) });
public ListenerPrimary(ServiceContext serviceContext) : base(serviceContext)
public ListenerPrimary(LibuvTransportContext transportContext) : base(transportContext)
{
}
@ -205,7 +206,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_bufPtr = _bufHandle.AddrOfPinnedObject();
}
public Libuv.uv_buf_t AllocCallback(UvStreamHandle dispatchPipe, int suggestedSize)
public LibuvFunctions.uv_buf_t AllocCallback(UvStreamHandle dispatchPipe, int suggestedSize)
{
return dispatchPipe.Libuv.buf_init(_bufPtr + _bytesRead, _bufferLength - _bytesRead);
}

Some files were not shown because too many files have changed in this diff Show More