245 lines
9.6 KiB
C#
245 lines
9.6 KiB
C#
// 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.Pipelines;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Hosting.Server;
|
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|
{
|
|
public class KestrelServer : IServer
|
|
{
|
|
private readonly List<ITransport> _transports = new List<ITransport>();
|
|
private readonly Heartbeat _heartbeat;
|
|
private readonly IServerAddressesFeature _serverAddresses;
|
|
private readonly ITransportFactory _transportFactory;
|
|
|
|
private bool _hasStarted;
|
|
private int _stopping;
|
|
private readonly TaskCompletionSource<object> _stoppedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
#pragma warning disable PUB0001 // Pubternal type in public API
|
|
public KestrelServer(IOptions<KestrelServerOptions> options, ITransportFactory transportFactory, ILoggerFactory loggerFactory)
|
|
#pragma warning restore PUB0001
|
|
: this(transportFactory, CreateServiceContext(options, loggerFactory))
|
|
{
|
|
}
|
|
|
|
// For testing
|
|
internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext)
|
|
{
|
|
if (transportFactory == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(transportFactory));
|
|
}
|
|
|
|
_transportFactory = transportFactory;
|
|
ServiceContext = serviceContext;
|
|
|
|
var httpHeartbeatManager = new HttpHeartbeatManager(serviceContext.ConnectionManager);
|
|
_heartbeat = new Heartbeat(
|
|
new IHeartbeatHandler[] { serviceContext.DateHeaderValueManager, httpHeartbeatManager },
|
|
serviceContext.SystemClock,
|
|
DebuggerWrapper.Singleton,
|
|
Trace);
|
|
|
|
Features = new FeatureCollection();
|
|
_serverAddresses = new ServerAddressesFeature();
|
|
Features.Set(_serverAddresses);
|
|
|
|
HttpCharacters.Initialize();
|
|
}
|
|
|
|
private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory)
|
|
{
|
|
if (options == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(options));
|
|
}
|
|
if (loggerFactory == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(loggerFactory));
|
|
}
|
|
|
|
var serverOptions = options.Value ?? new KestrelServerOptions();
|
|
var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel");
|
|
var trace = new KestrelTrace(logger);
|
|
var connectionManager = new HttpConnectionManager(
|
|
trace,
|
|
serverOptions.Limits.MaxConcurrentUpgradedConnections);
|
|
|
|
var systemClock = new SystemClock();
|
|
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
|
|
|
// TODO: This logic will eventually move into the IConnectionHandler<T> and off
|
|
// the service context once we get to https://github.com/aspnet/KestrelHttpServer/issues/1662
|
|
PipeScheduler scheduler = null;
|
|
switch (serverOptions.ApplicationSchedulingMode)
|
|
{
|
|
case SchedulingMode.Default:
|
|
case SchedulingMode.ThreadPool:
|
|
scheduler = PipeScheduler.ThreadPool;
|
|
break;
|
|
case SchedulingMode.Inline:
|
|
scheduler = PipeScheduler.Inline;
|
|
break;
|
|
default:
|
|
throw new NotSupportedException(CoreStrings.FormatUnknownTransportMode(serverOptions.ApplicationSchedulingMode));
|
|
}
|
|
|
|
return new ServiceContext
|
|
{
|
|
Log = trace,
|
|
HttpParser = new HttpParser<Http1ParsingHandler>(trace.IsEnabled(LogLevel.Information)),
|
|
Scheduler = scheduler,
|
|
SystemClock = systemClock,
|
|
DateHeaderValueManager = dateHeaderValueManager,
|
|
ConnectionManager = connectionManager,
|
|
ServerOptions = serverOptions
|
|
};
|
|
}
|
|
|
|
public IFeatureCollection Features { get; }
|
|
|
|
public KestrelServerOptions Options => ServiceContext.ServerOptions;
|
|
|
|
private ServiceContext ServiceContext { get; }
|
|
|
|
private IKestrelTrace Trace => ServiceContext.Log;
|
|
|
|
private HttpConnectionManager ConnectionManager => ServiceContext.ConnectionManager;
|
|
|
|
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
if (!BitConverter.IsLittleEndian)
|
|
{
|
|
throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
|
|
}
|
|
|
|
ValidateOptions();
|
|
|
|
if (_hasStarted)
|
|
{
|
|
// The server has already started and/or has not been cleaned up yet
|
|
throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
|
|
}
|
|
_hasStarted = true;
|
|
_heartbeat.Start();
|
|
|
|
async Task OnBind(ListenOptions endpoint)
|
|
{
|
|
// Add the HTTP middleware as the terminal connection middleware
|
|
endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application, endpoint.Protocols);
|
|
|
|
var connectionDelegate = endpoint.Build();
|
|
|
|
// Add the connection limit middleware
|
|
if (Options.Limits.MaxConcurrentConnections.HasValue)
|
|
{
|
|
connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
|
|
}
|
|
|
|
var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
|
|
var transport = _transportFactory.Create(endpoint, connectionDispatcher);
|
|
_transports.Add(transport);
|
|
|
|
await transport.BindAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Trace.LogCritical(0, ex, "Unable to start Kestrel.");
|
|
Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Graceful shutdown if possible
|
|
public async Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
if (Interlocked.Exchange(ref _stopping, 1) == 1)
|
|
{
|
|
await _stoppedTcs.Task.ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var tasks = new Task[_transports.Count];
|
|
for (int i = 0; i < _transports.Count; i++)
|
|
{
|
|
tasks[i] = _transports[i].UnbindAsync();
|
|
}
|
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
|
|
if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false))
|
|
{
|
|
Trace.NotAllConnectionsClosedGracefully();
|
|
|
|
if (!await ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false))
|
|
{
|
|
Trace.NotAllConnectionsAborted();
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _transports.Count; i++)
|
|
{
|
|
tasks[i] = _transports[i].StopAsync();
|
|
}
|
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
|
|
_heartbeat.Dispose();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_stoppedTcs.TrySetException(ex);
|
|
throw;
|
|
}
|
|
|
|
_stoppedTcs.TrySetResult(null);
|
|
}
|
|
|
|
// Ungraceful shutdown
|
|
public void Dispose()
|
|
{
|
|
var cancelledTokenSource = new CancellationTokenSource();
|
|
cancelledTokenSource.Cancel();
|
|
StopAsync(cancelledTokenSource.Token).GetAwaiter().GetResult();
|
|
}
|
|
|
|
private void ValidateOptions()
|
|
{
|
|
Options.ConfigurationLoader?.Load();
|
|
|
|
if (Options.Limits.MaxRequestBufferSize.HasValue &&
|
|
Options.Limits.MaxRequestBufferSize < Options.Limits.MaxRequestLineSize)
|
|
{
|
|
throw new InvalidOperationException(
|
|
CoreStrings.FormatMaxRequestBufferSmallerThanRequestLineBuffer(Options.Limits.MaxRequestBufferSize.Value, Options.Limits.MaxRequestLineSize));
|
|
}
|
|
|
|
if (Options.Limits.MaxRequestBufferSize.HasValue &&
|
|
Options.Limits.MaxRequestBufferSize < Options.Limits.MaxRequestHeadersTotalSize)
|
|
{
|
|
throw new InvalidOperationException(
|
|
CoreStrings.FormatMaxRequestBufferSmallerThanRequestHeaderBuffer(Options.Limits.MaxRequestBufferSize.Value, Options.Limits.MaxRequestHeadersTotalSize));
|
|
}
|
|
}
|
|
}
|
|
}
|