Merge branch 'release' into dev
This commit is contained in:
commit
38c70a6fec
|
|
@ -3,12 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.PlatformAbstractions;
|
using Microsoft.Extensions.PlatformAbstractions;
|
||||||
|
|
||||||
|
|
@ -18,25 +15,8 @@ namespace SampleApp
|
||||||
{
|
{
|
||||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IApplicationEnvironment env)
|
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IApplicationEnvironment env)
|
||||||
{
|
{
|
||||||
var ksi = app.ServerFeatures.Get<IKestrelServerInformation>();
|
|
||||||
//ksi.ThreadCount = 4;
|
|
||||||
ksi.NoDelay = true;
|
|
||||||
|
|
||||||
loggerFactory.AddConsole(LogLevel.Trace);
|
loggerFactory.AddConsole(LogLevel.Trace);
|
||||||
|
|
||||||
var testCertPath = Path.Combine(env.ApplicationBasePath, "testCert.pfx");
|
|
||||||
|
|
||||||
if (File.Exists(testCertPath))
|
|
||||||
{
|
|
||||||
app.UseKestrelHttps(new X509Certificate2(testCertPath, "testPassword"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("Could not find certificate at '{0}'. HTTPS is not enabled.", testCertPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseKestrelConnectionLogging();
|
|
||||||
|
|
||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
Console.WriteLine("{0} {1}{2}{3}",
|
Console.WriteLine("{0} {1}{2}{3}",
|
||||||
|
|
@ -63,7 +43,13 @@ namespace SampleApp
|
||||||
{
|
{
|
||||||
var host = new WebHostBuilder()
|
var host = new WebHostBuilder()
|
||||||
.UseDefaultHostingConfiguration(args)
|
.UseDefaultHostingConfiguration(args)
|
||||||
.UseKestrel()
|
.UseKestrel(options =>
|
||||||
|
{
|
||||||
|
// options.ThreadCount = 4;
|
||||||
|
options.NoDelay = true;
|
||||||
|
options.UseHttps("testCert.pfx", "testPassword");
|
||||||
|
options.UseConnectionLogging();
|
||||||
|
})
|
||||||
.UseUrls("http://localhost:5000", "https://localhost:5001")
|
.UseUrls("http://localhost:5000", "https://localhost:5001")
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter
|
|
||||||
{
|
|
||||||
public static class HttpsApplicationBuilderExtensions
|
|
||||||
{
|
|
||||||
public static IApplicationBuilder UseKestrelHttps(this IApplicationBuilder app, X509Certificate2 cert)
|
|
||||||
{
|
|
||||||
return app.UseKestrelHttps(new HttpsConnectionFilterOptions { ServerCertificate = cert});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IApplicationBuilder UseKestrelHttps(this IApplicationBuilder app, HttpsConnectionFilterOptions options)
|
|
||||||
{
|
|
||||||
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
|
|
||||||
|
|
||||||
if (serverInfo == null)
|
|
||||||
{
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
var prevFilter = serverInfo.ConnectionFilter ?? new NoOpConnectionFilter();
|
|
||||||
|
|
||||||
serverInfo.ConnectionFilter = new HttpsConnectionFilter(options, prevFilter);
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
// 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.Security.Cryptography.X509Certificates;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Hosting
|
||||||
|
{
|
||||||
|
public static class KestrelServerOptionsHttpsExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configure Kestrel to use HTTPS.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="fileName">
|
||||||
|
/// The name of a certificate file, relative to the directory that contains the application content files.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||||
|
/// </returns>
|
||||||
|
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, string fileName)
|
||||||
|
{
|
||||||
|
var env = options.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||||
|
return options.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure Kestrel to use HTTPS.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="fileName">
|
||||||
|
/// The name of a certificate file, relative to the directory that contains the application content files.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="password">
|
||||||
|
/// The password required to access the X.509 certificate data.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||||
|
/// </returns>
|
||||||
|
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, string fileName, string password)
|
||||||
|
{
|
||||||
|
var env = options.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||||
|
return options.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName), password));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure Kestrel to use HTTPS.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="serverCertificate">
|
||||||
|
/// The X.509 certificate.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||||
|
/// </returns>
|
||||||
|
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, X509Certificate2 serverCertificate)
|
||||||
|
{
|
||||||
|
return options.UseHttps(new HttpsConnectionFilterOptions { ServerCertificate = serverCertificate });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure Kestrel to use HTTPS.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="httpsOptions">
|
||||||
|
/// Options to configure HTTPS.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||||
|
/// </returns>
|
||||||
|
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, HttpsConnectionFilterOptions httpsOptions)
|
||||||
|
{
|
||||||
|
var prevFilter = options.ConnectionFilter ?? new NoOpConnectionFilter();
|
||||||
|
options.ConnectionFilter = new HttpsConnectionFilter(httpsOptions, prevFilter);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// 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;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Hosting
|
||||||
|
{
|
||||||
|
public static class KestrelServerOptionsConnectionLoggingExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Emits verbose logs for bytes read from and written to the connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||||
|
/// </returns>
|
||||||
|
public static KestrelServerOptions UseConnectionLogging(this KestrelServerOptions options)
|
||||||
|
{
|
||||||
|
return options.UseConnectionLogging(nameof(LoggingConnectionFilter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emits verbose logs for bytes read from and written to the connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||||
|
/// </returns>
|
||||||
|
public static KestrelServerOptions UseConnectionLogging(this KestrelServerOptions options, string loggerName)
|
||||||
|
{
|
||||||
|
var prevFilter = options.ConnectionFilter ?? new NoOpConnectionFilter();
|
||||||
|
var loggerFactory = options.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||||
|
var logger = loggerFactory.CreateLogger(loggerName ?? nameof(LoggingConnectionFilter));
|
||||||
|
options.ConnectionFilter = new LoggingConnectionFilter(logger, prevFilter);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter
|
|
||||||
{
|
|
||||||
public static class LoggingFilterApplicationBuilderExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Emits verbose logs for bytes read from and written to the connection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static IApplicationBuilder UseKestrelConnectionLogging(this IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
return app.UseKestrelConnectionLogging(nameof(LoggingConnectionFilter));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Emits verbose logs for bytes read from and written to the connection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static IApplicationBuilder UseKestrelConnectionLogging(this IApplicationBuilder app, string loggerName)
|
|
||||||
{
|
|
||||||
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
|
|
||||||
if (serverInfo != null)
|
|
||||||
{
|
|
||||||
var prevFilter = serverInfo.ConnectionFilter ?? new NoOpConnectionFilter();
|
|
||||||
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
|
||||||
var logger = loggerFactory.CreateLogger(loggerName ?? nameof(LoggingConnectionFilter));
|
|
||||||
serverInfo.ConnectionFilter = new LoggingConnectionFilter(logger, prevFilter);
|
|
||||||
}
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't initialize _frame until SocketInput and SocketOutput are set to their final values.
|
// Don't initialize _frame until SocketInput and SocketOutput are set to their final values.
|
||||||
if (ServerInformation.ConnectionFilter == null)
|
if (ServerOptions.ConnectionFilter == null)
|
||||||
{
|
{
|
||||||
lock (_stateLock)
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
|
|
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ServerInformation.ConnectionFilter.OnConnectionAsync(_filterContext).ContinueWith((task, state) =>
|
ServerOptions.ConnectionFilter.OnConnectionAsync(_filterContext).ContinueWith((task, state) =>
|
||||||
{
|
{
|
||||||
var connection = (Connection)state;
|
var connection = (Connection)state;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
var socket = new UvTcpHandle(Log);
|
var socket = new UvTcpHandle(Log);
|
||||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||||
socket.NoDelay(ServerInformation.NoDelay);
|
socket.NoDelay(ServerOptions.NoDelay);
|
||||||
socket.Bind(ServerAddress);
|
socket.Bind(ServerAddress);
|
||||||
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
|
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
|
||||||
return socket;
|
return socket;
|
||||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||||
acceptSocket.NoDelay(ServerInformation.NoDelay);
|
acceptSocket.NoDelay(ServerOptions.NoDelay);
|
||||||
listenSocket.Accept(acceptSocket);
|
listenSocket.Accept(acceptSocket);
|
||||||
DispatchConnection(acceptSocket);
|
DispatchConnection(acceptSocket);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
var socket = new UvTcpHandle(Log);
|
var socket = new UvTcpHandle(Log);
|
||||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||||
socket.NoDelay(ServerInformation.NoDelay);
|
socket.NoDelay(ServerOptions.NoDelay);
|
||||||
socket.Bind(ServerAddress);
|
socket.Bind(ServerAddress);
|
||||||
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
|
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
|
||||||
return socket;
|
return socket;
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||||
acceptSocket.NoDelay(ServerInformation.NoDelay);
|
acceptSocket.NoDelay(ServerOptions.NoDelay);
|
||||||
listenSocket.Accept(acceptSocket);
|
listenSocket.Accept(acceptSocket);
|
||||||
DispatchConnection(acceptSocket);
|
DispatchConnection(acceptSocket);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
var acceptSocket = new UvTcpHandle(Log);
|
var acceptSocket = new UvTcpHandle(Log);
|
||||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||||
acceptSocket.NoDelay(ServerInformation.NoDelay);
|
acceptSocket.NoDelay(ServerOptions.NoDelay);
|
||||||
return acceptSocket;
|
return acceptSocket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel
|
|
||||||
{
|
|
||||||
public interface IKestrelServerInformation
|
|
||||||
{
|
|
||||||
int ThreadCount { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The amount of time after the server begins shutting down before connections will be forcefully closed.
|
|
||||||
/// By default, Kestrel will wait 5 seconds for any ongoing requests to complete before terminating
|
|
||||||
/// the connection.
|
|
||||||
/// A custom timeout can be configured using the "kestrel.shutdownTimeout" key in <seealso cref="Microsoft.Extensions.Configuration.IConfiguration"/>.
|
|
||||||
/// The value will be parsed as a float representing the timeout in seconds.
|
|
||||||
/// </summary>
|
|
||||||
TimeSpan ShutdownTimeout { get; set; }
|
|
||||||
|
|
||||||
bool NoDelay { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets values that instruct <seealso cref="KestrelServer"/> whether it is safe to
|
|
||||||
/// pool the Request and Response <seealso cref="System.IO.Stream"/> objects, Headers etc
|
|
||||||
/// for another request after the Response's OnCompleted callback has fired.
|
|
||||||
/// When these values are greater than zero it is not safe to retain references to feature components after this event has fired.
|
|
||||||
/// They are zero by default.
|
|
||||||
/// </summary>
|
|
||||||
KestrelServerPoolingParameters PoolingParameters { get; }
|
|
||||||
|
|
||||||
IConnectionFilter ConnectionFilter { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,11 +13,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
||||||
private ConcurrentQueue<Streams> _streamPool = new ConcurrentQueue<Streams>();
|
private ConcurrentQueue<Streams> _streamPool = new ConcurrentQueue<Streams>();
|
||||||
private ConcurrentQueue<Headers> _headerPool = new ConcurrentQueue<Headers>();
|
private ConcurrentQueue<Headers> _headerPool = new ConcurrentQueue<Headers>();
|
||||||
|
|
||||||
public IKestrelServerInformation ServerInformation { get; set; }
|
public KestrelServerOptions ServerOptions { get; set; }
|
||||||
|
|
||||||
public HttpComponentFactory(IKestrelServerInformation serverInformation)
|
public HttpComponentFactory(KestrelServerOptions serverOptions)
|
||||||
{
|
{
|
||||||
ServerInformation = serverInformation;
|
ServerOptions = serverOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Streams CreateStreams(FrameContext owner)
|
public Streams CreateStreams(FrameContext owner)
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
||||||
|
|
||||||
public void DisposeStreams(Streams streams)
|
public void DisposeStreams(Streams streams)
|
||||||
{
|
{
|
||||||
if (_streamPool.Count < ServerInformation.PoolingParameters.MaxPooledStreams)
|
if (_streamPool.Count < ServerOptions.MaxPooledStreams)
|
||||||
{
|
{
|
||||||
streams.Uninitialize();
|
streams.Uninitialize();
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
||||||
|
|
||||||
public void DisposeHeaders(Headers headers)
|
public void DisposeHeaders(Headers headers)
|
||||||
{
|
{
|
||||||
if (_headerPool.Count < ServerInformation.PoolingParameters.MaxPooledHeaders)
|
if (_headerPool.Count < ServerOptions.MaxPooledHeaders)
|
||||||
{
|
{
|
||||||
headers.Uninitialize();
|
headers.Uninitialize();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
|
||||||
{
|
{
|
||||||
interface IHttpComponentFactory
|
interface IHttpComponentFactory
|
||||||
{
|
{
|
||||||
IKestrelServerInformation ServerInformation { get; set; }
|
KestrelServerOptions ServerOptions { get; set; }
|
||||||
|
|
||||||
Streams CreateStreams(FrameContext owner);
|
Streams CreateStreams(FrameContext owner);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
||||||
|
{
|
||||||
|
public class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
|
||||||
|
{
|
||||||
|
private IServiceProvider _services;
|
||||||
|
|
||||||
|
public KestrelServerOptionsSetup(IServiceProvider services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(KestrelServerOptions options)
|
||||||
|
{
|
||||||
|
options.ApplicationServices = _services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
//// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
//// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Server.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
||||||
|
{
|
||||||
|
public class ServerAddressesFeature : IServerAddressesFeature
|
||||||
|
{
|
||||||
|
public ICollection<string> Addresses { get; } = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
disposeTasks.Add(listener.DisposeAsync());
|
disposeTasks.Add(listener.DisposeAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Task.WhenAll(disposeTasks).Wait(ServerInformation.ShutdownTimeout))
|
if (!Task.WhenAll(disposeTasks).Wait(ServerOptions.ShutdownTimeout))
|
||||||
{
|
{
|
||||||
Log.NotAllConnectionsClosedGracefully();
|
Log.NotAllConnectionsClosedGracefully();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -18,13 +19,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
private readonly IApplicationLifetime _applicationLifetime;
|
private readonly IApplicationLifetime _applicationLifetime;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public KestrelServer(IFeatureCollection features, IApplicationLifetime applicationLifetime, ILogger logger)
|
public KestrelServer(IFeatureCollection features, KestrelServerOptions options, IApplicationLifetime applicationLifetime, ILogger logger)
|
||||||
{
|
{
|
||||||
if (features == null)
|
if (features == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(features));
|
throw new ArgumentNullException(nameof(features));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
if (applicationLifetime == null)
|
if (applicationLifetime == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(applicationLifetime));
|
throw new ArgumentNullException(nameof(applicationLifetime));
|
||||||
|
|
@ -38,10 +44,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
_applicationLifetime = applicationLifetime;
|
_applicationLifetime = applicationLifetime;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
Features = features;
|
Features = features;
|
||||||
|
Options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFeatureCollection Features { get; }
|
public IFeatureCollection Features { get; }
|
||||||
|
|
||||||
|
public KestrelServerOptions Options { get; }
|
||||||
|
|
||||||
public void Start<TContext>(IHttpApplication<TContext> application)
|
public void Start<TContext>(IHttpApplication<TContext> application)
|
||||||
{
|
{
|
||||||
if (_disposables != null)
|
if (_disposables != null)
|
||||||
|
|
@ -53,7 +62,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var information = (KestrelServerInformation)Features.Get<IKestrelServerInformation>();
|
|
||||||
var componentFactory = Features.Get<IHttpComponentFactory>();
|
var componentFactory = Features.Get<IHttpComponentFactory>();
|
||||||
var dateHeaderValueManager = new DateHeaderValueManager();
|
var dateHeaderValueManager = new DateHeaderValueManager();
|
||||||
var trace = new KestrelTrace(_logger);
|
var trace = new KestrelTrace(_logger);
|
||||||
|
|
@ -67,14 +75,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
Log = trace,
|
Log = trace,
|
||||||
ThreadPool = new LoggingThreadPool(trace),
|
ThreadPool = new LoggingThreadPool(trace),
|
||||||
DateHeaderValueManager = dateHeaderValueManager,
|
DateHeaderValueManager = dateHeaderValueManager,
|
||||||
ServerInformation = information,
|
ServerOptions = Options,
|
||||||
HttpComponentFactory = componentFactory
|
HttpComponentFactory = componentFactory
|
||||||
});
|
});
|
||||||
|
|
||||||
_disposables.Push(engine);
|
_disposables.Push(engine);
|
||||||
_disposables.Push(dateHeaderValueManager);
|
_disposables.Push(dateHeaderValueManager);
|
||||||
|
|
||||||
var threadCount = information.ThreadCount;
|
var threadCount = Options.ThreadCount;
|
||||||
|
|
||||||
if (threadCount <= 0)
|
if (threadCount <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +94,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
engine.Start(threadCount);
|
engine.Start(threadCount);
|
||||||
var atLeastOneListener = false;
|
var atLeastOneListener = false;
|
||||||
|
|
||||||
foreach (var address in information.Addresses)
|
var addressesFeature = Features.Get<IServerAddressesFeature>();
|
||||||
|
if (addressesFeature == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{nameof(IServerAddressesFeature)} is missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var address in addressesFeature.Addresses)
|
||||||
{
|
{
|
||||||
var parsedAddress = ServerAddress.FromUrl(address);
|
var parsedAddress = ServerAddress.FromUrl(address);
|
||||||
if (parsedAddress == null)
|
if (parsedAddress == null)
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.AspNetCore.Server.Features;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel
|
|
||||||
{
|
|
||||||
public class KestrelServerInformation : IKestrelServerInformation, IServerAddressesFeature
|
|
||||||
{
|
|
||||||
public KestrelServerInformation(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
if (configuration == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(configuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
Addresses = GetAddresses(configuration);
|
|
||||||
ThreadCount = GetThreadCount(configuration);
|
|
||||||
ShutdownTimeout = GetShutdownTimeout(configuration);
|
|
||||||
NoDelay = GetNoDelay(configuration);
|
|
||||||
PoolingParameters = new KestrelServerPoolingParameters(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICollection<string> Addresses { get; }
|
|
||||||
|
|
||||||
public int ThreadCount { get; set; }
|
|
||||||
|
|
||||||
public TimeSpan ShutdownTimeout { get; set; }
|
|
||||||
|
|
||||||
public bool NoDelay { get; set; }
|
|
||||||
|
|
||||||
public KestrelServerPoolingParameters PoolingParameters { get; }
|
|
||||||
|
|
||||||
public IConnectionFilter ConnectionFilter { get; set; }
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ICollection<string> GetAddresses(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var addresses = new List<string>();
|
|
||||||
|
|
||||||
var urls = configuration["server.urls"];
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(urls))
|
|
||||||
{
|
|
||||||
addresses.AddRange(urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
|
|
||||||
}
|
|
||||||
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetThreadCount(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var threadCountString = configuration["kestrel.threadCount"];
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(threadCountString))
|
|
||||||
{
|
|
||||||
return ProcessorThreadCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
int threadCount;
|
|
||||||
if (int.TryParse(threadCountString, NumberStyles.Integer, CultureInfo.InvariantCulture, out threadCount))
|
|
||||||
{
|
|
||||||
return threadCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ProcessorThreadCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TimeSpan GetShutdownTimeout(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var shutdownTimeoutString = configuration["kestrel.shutdownTimeout"];
|
|
||||||
|
|
||||||
float shutdownTimeout;
|
|
||||||
if (float.TryParse(shutdownTimeoutString, NumberStyles.Float, CultureInfo.InvariantCulture, out shutdownTimeout))
|
|
||||||
{
|
|
||||||
return TimeSpan.FromSeconds(shutdownTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TimeSpan.FromSeconds(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool GetNoDelay(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var noDelayString = configuration["kestrel.noDelay"];
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(noDelayString))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool noDelay;
|
|
||||||
if (bool.TryParse(noDelayString, out noDelay))
|
|
||||||
{
|
|
||||||
return noDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
// 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.Filter;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
|
{
|
||||||
|
public class KestrelServerOptions
|
||||||
|
{
|
||||||
|
public IServiceProvider ApplicationServices { get; set; }
|
||||||
|
|
||||||
|
public IConnectionFilter ConnectionFilter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets value that instructs <seealso cref="KestrelServer"/> whether it is safe to
|
||||||
|
/// pool the Request and Response <seealso cref="System.IO.Stream"/> objects
|
||||||
|
/// for another request after the Response's OnCompleted callback has fired.
|
||||||
|
/// When this values is greater than zero, it is not safe to retain references to feature components after this event has fired.
|
||||||
|
/// Value is zero by default.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxPooledStreams { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets value that instructs <seealso cref="KestrelServer"/> whether it is safe to
|
||||||
|
/// pool the Request and Response headers
|
||||||
|
/// for another request after the Response's OnCompleted callback has fired.
|
||||||
|
/// When this values is greater than zero, it is not safe to retain references to feature components after this event has fired.
|
||||||
|
/// Value is zero by default.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxPooledHeaders { get; set; }
|
||||||
|
|
||||||
|
public bool NoDelay { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time after the server begins shutting down before connections will be forcefully closed.
|
||||||
|
/// By default, Kestrel will wait 5 seconds for any ongoing requests to complete before terminating
|
||||||
|
/// the connection.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel
|
|
||||||
{
|
|
||||||
public class KestrelServerPoolingParameters
|
|
||||||
{
|
|
||||||
public KestrelServerPoolingParameters(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
if (configuration == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(configuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
MaxPooledStreams = GetPooledCount(configuration["kestrel.maxPooledStreams"]);
|
|
||||||
MaxPooledHeaders = GetPooledCount(configuration["kestrel.maxPooledHeaders"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int MaxPooledStreams { get; set; }
|
|
||||||
|
|
||||||
public int MaxPooledHeaders { get; set; }
|
|
||||||
|
|
||||||
private static int GetPooledCount(string countString)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(countString))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count;
|
|
||||||
if (int.TryParse(countString, NumberStyles.Integer, CultureInfo.InvariantCulture, out count))
|
|
||||||
{
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Hosting
|
|
||||||
{
|
|
||||||
public static class KestrelWebHostBuilderExtensions
|
|
||||||
{
|
|
||||||
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
|
|
||||||
{
|
|
||||||
return hostBuilder.UseServer(typeof(KestrelServer).GetTypeInfo().Assembly.FullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,8 +7,10 @@ using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Features;
|
using Microsoft.AspNetCore.Server.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel
|
namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
{
|
{
|
||||||
|
|
@ -19,22 +21,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
{
|
{
|
||||||
private readonly IApplicationLifetime _appLifetime;
|
private readonly IApplicationLifetime _appLifetime;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
private readonly KestrelServerOptions _options;
|
||||||
|
|
||||||
public ServerFactory(IApplicationLifetime appLifetime, ILoggerFactory loggerFactory)
|
public ServerFactory(IApplicationLifetime appLifetime, ILoggerFactory loggerFactory, IOptions<KestrelServerOptions> optionsAccessor)
|
||||||
{
|
{
|
||||||
_appLifetime = appLifetime;
|
_appLifetime = appLifetime;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
|
_options = optionsAccessor.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IServer CreateServer(IConfiguration configuration)
|
public IServer CreateServer(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var information = new KestrelServerInformation(configuration);
|
var componentFactory = new HttpComponentFactory(_options);
|
||||||
var componentFactory = new HttpComponentFactory(information);
|
|
||||||
var serverFeatures = new FeatureCollection();
|
var serverFeatures = new FeatureCollection();
|
||||||
serverFeatures.Set<IKestrelServerInformation>(information);
|
|
||||||
serverFeatures.Set<IHttpComponentFactory>(componentFactory);
|
serverFeatures.Set<IHttpComponentFactory>(componentFactory);
|
||||||
serverFeatures.Set<IServerAddressesFeature>(information);
|
serverFeatures.Set(GetAddresses(configuration));
|
||||||
return new KestrelServer(serverFeatures, _appLifetime, _loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"));
|
return new KestrelServer(serverFeatures, _options, _appLifetime, _loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IServerAddressesFeature GetAddresses(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var addressesFeature = new ServerAddressesFeature();
|
||||||
|
var urls = configuration["server.urls"];
|
||||||
|
if (!string.IsNullOrEmpty(urls))
|
||||||
|
{
|
||||||
|
foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
addressesFeature.Addresses.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addressesFeature;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
ThreadPool = context.ThreadPool;
|
ThreadPool = context.ThreadPool;
|
||||||
FrameFactory = context.FrameFactory;
|
FrameFactory = context.FrameFactory;
|
||||||
DateHeaderValueManager = context.DateHeaderValueManager;
|
DateHeaderValueManager = context.DateHeaderValueManager;
|
||||||
ServerInformation = context.ServerInformation;
|
ServerOptions = context.ServerOptions;
|
||||||
HttpComponentFactory = context.HttpComponentFactory;
|
HttpComponentFactory = context.HttpComponentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
|
|
||||||
public DateHeaderValueManager DateHeaderValueManager { get; set; }
|
public DateHeaderValueManager DateHeaderValueManager { get; set; }
|
||||||
|
|
||||||
public IKestrelServerInformation ServerInformation { get; set; }
|
public KestrelServerOptions ServerOptions { get; set; }
|
||||||
|
|
||||||
internal IHttpComponentFactory HttpComponentFactory { get; set; }
|
internal IHttpComponentFactory HttpComponentFactory { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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.Hosting.Server;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Hosting
|
||||||
|
{
|
||||||
|
public static class WebHostBuilderKestrelExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specify Kestrel as the server to be used by the web host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostBuilder">
|
||||||
|
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
|
||||||
|
/// </returns>
|
||||||
|
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
|
||||||
|
{
|
||||||
|
return hostBuilder.ConfigureServices(services => services.AddSingleton<IServerFactory, ServerFactory>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify Kestrel as the server to be used by the web host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostBuilder">
|
||||||
|
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// A callback to configure Kestrel options.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
|
||||||
|
/// </returns>
|
||||||
|
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action<KestrelServerOptions> options)
|
||||||
|
{
|
||||||
|
hostBuilder.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
|
||||||
|
services.Configure(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
return hostBuilder.UseKestrel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,11 +26,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
|
||||||
var hostBuilder = new WebHostBuilder()
|
var hostBuilder = new WebHostBuilder()
|
||||||
.UseConfiguration(config)
|
.UseConfiguration(config)
|
||||||
.UseKestrel()
|
.UseKestrel(options =>
|
||||||
|
{
|
||||||
|
options.ThreadCount = threadCount;
|
||||||
|
})
|
||||||
.Configure(app =>
|
.Configure(app =>
|
||||||
{
|
{
|
||||||
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
|
|
||||||
serverInfo.ThreadCount = threadCount;
|
|
||||||
app.Run(context =>
|
app.Run(context =>
|
||||||
{
|
{
|
||||||
return context.Response.WriteAsync("Hello World");
|
return context.Response.WriteAsync("Hello World");
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[MemberData(nameof(ConnectionFilterData))]
|
[MemberData(nameof(ConnectionFilterData))]
|
||||||
public async Task ReuseStreamsOn(ServiceContext testContext)
|
public async Task ReuseStreamsOn(ServiceContext testContext)
|
||||||
{
|
{
|
||||||
testContext.ServerInformation.PoolingParameters.MaxPooledStreams = 120;
|
testContext.ServerOptions.MaxPooledStreams = 120;
|
||||||
|
|
||||||
var streamCount = 0;
|
var streamCount = 0;
|
||||||
var loopCount = 20;
|
var loopCount = 20;
|
||||||
|
|
@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[MemberData(nameof(ConnectionFilterData))]
|
[MemberData(nameof(ConnectionFilterData))]
|
||||||
public async Task ReuseStreamsOff(ServiceContext testContext)
|
public async Task ReuseStreamsOff(ServiceContext testContext)
|
||||||
{
|
{
|
||||||
testContext.ServerInformation.PoolingParameters.MaxPooledStreams = 0;
|
testContext.ServerOptions.MaxPooledStreams = 0;
|
||||||
|
|
||||||
var streamCount = 0;
|
var streamCount = 0;
|
||||||
var loopCount = 20;
|
var loopCount = 20;
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InitialDictionaryContainsServerAndDate()
|
public void InitialDictionaryContainsServerAndDate()
|
||||||
{
|
{
|
||||||
var configuration = new ConfigurationBuilder().Build();
|
var serverOptions = new KestrelServerOptions();
|
||||||
var serverInformation = new KestrelServerInformation(configuration);
|
|
||||||
var connectionContext = new ConnectionContext
|
var connectionContext = new ConnectionContext
|
||||||
{
|
{
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
ServerInformation = serverInformation,
|
ServerOptions = serverOptions,
|
||||||
HttpComponentFactory = new HttpComponentFactory(serverInformation)
|
HttpComponentFactory = new HttpComponentFactory(serverOptions)
|
||||||
};
|
};
|
||||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
frame.InitializeHeaders();
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
|
|
@ -51,14 +50,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InitialEntriesCanBeCleared()
|
public void InitialEntriesCanBeCleared()
|
||||||
{
|
{
|
||||||
var configuration = new ConfigurationBuilder().Build();
|
var serverOptions = new KestrelServerOptions();
|
||||||
var serverInformation = new KestrelServerInformation(configuration);
|
|
||||||
var connectionContext = new ConnectionContext
|
var connectionContext = new ConnectionContext
|
||||||
{
|
{
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||||
ServerInformation = serverInformation,
|
ServerOptions = serverOptions,
|
||||||
HttpComponentFactory = new HttpComponentFactory(serverInformation)
|
HttpComponentFactory = new HttpComponentFactory(serverOptions)
|
||||||
};
|
};
|
||||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
frame.InitializeHeaders();
|
frame.InitializeHeaders();
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
|
||||||
{
|
|
||||||
public class KestrelServerInformationTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void NullConfigurationThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<ArgumentNullException>(() => new KestrelServerInformation(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetThreadCountUsingConfiguration()
|
|
||||||
{
|
|
||||||
const int expected = 42;
|
|
||||||
|
|
||||||
var values = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "kestrel.threadCount", expected.ToString() }
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.AddInMemoryCollection(values)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var information = new KestrelServerInformation(configuration);
|
|
||||||
|
|
||||||
Assert.Equal(expected, information.ThreadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetThreadCountUsingProcessorCount()
|
|
||||||
{
|
|
||||||
// Ideally we'd mock Environment.ProcessorCount to test edge cases.
|
|
||||||
var expected = Clamp(Environment.ProcessorCount >> 1, 1, 16);
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder().Build();
|
|
||||||
|
|
||||||
var information = new KestrelServerInformation(configuration);
|
|
||||||
|
|
||||||
Assert.Equal(expected, information.ThreadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetAddressesUsingConfiguration()
|
|
||||||
{
|
|
||||||
var expected = new List<string> { "http://localhost:1337", "https://localhost:42" };
|
|
||||||
|
|
||||||
var values = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "server.urls", string.Join(";", expected) }
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.AddInMemoryCollection(values)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var information = new KestrelServerInformation(configuration);
|
|
||||||
|
|
||||||
Assert.Equal(expected, information.Addresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetNoDelayUsingConfiguration()
|
|
||||||
{
|
|
||||||
var values = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "kestrel.noDelay", "false" }
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.AddInMemoryCollection(values)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var information = new KestrelServerInformation(configuration);
|
|
||||||
|
|
||||||
Assert.False(information.NoDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(null, 0)]
|
|
||||||
[InlineData("", 0)]
|
|
||||||
[InlineData("0", 0)]
|
|
||||||
[InlineData("00", 0)]
|
|
||||||
[InlineData("0.0", 0)]
|
|
||||||
[InlineData("1", 1)]
|
|
||||||
[InlineData("16", 16)]
|
|
||||||
[InlineData("1000", 1000)]
|
|
||||||
public void SetMaxPooledStreamsUsingConfiguration(string input, int expected)
|
|
||||||
{
|
|
||||||
var values = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "kestrel.maxPooledStreams", input }
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.AddInMemoryCollection(values)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var information = new KestrelServerInformation(configuration);
|
|
||||||
|
|
||||||
Assert.Equal(expected, information.PoolingParameters.MaxPooledStreams);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(null, 0)]
|
|
||||||
[InlineData("", 0)]
|
|
||||||
[InlineData("0", 0)]
|
|
||||||
[InlineData("00", 0)]
|
|
||||||
[InlineData("0.0", 0)]
|
|
||||||
[InlineData("1", 1)]
|
|
||||||
[InlineData("16", 16)]
|
|
||||||
[InlineData("1000", 1000)]
|
|
||||||
public void SetMaxPooledHeadersUsingConfiguration(string input, int expected)
|
|
||||||
{
|
|
||||||
var values = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "kestrel.maxPooledHeaders", input }
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.AddInMemoryCollection(values)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var information = new KestrelServerInformation(configuration);
|
|
||||||
|
|
||||||
Assert.Equal(expected, information.PoolingParameters.MaxPooledHeaders);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int Clamp(int value, int min, int max)
|
|
||||||
{
|
|
||||||
return value < min ? min : value > max ? max : value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Server.Kestrel;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
{
|
||||||
|
public class KestrelServerInformationTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SetThreadCountUsingProcessorCount()
|
||||||
|
{
|
||||||
|
// Ideally we'd mock Environment.ProcessorCount to test edge cases.
|
||||||
|
var expected = Clamp(Environment.ProcessorCount >> 1, 1, 16);
|
||||||
|
|
||||||
|
var information = new KestrelServerOptions();
|
||||||
|
|
||||||
|
Assert.Equal(expected, information.ThreadCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Clamp(int value, int min, int max)
|
||||||
|
{
|
||||||
|
return value < min ? min : value > max ? max : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Server.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
|
|
@ -18,11 +19,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[InlineData(-1337)]
|
[InlineData(-1337)]
|
||||||
public void StartWithNonPositiveThreadCountThrows(int threadCount)
|
public void StartWithNonPositiveThreadCountThrows(int threadCount)
|
||||||
{
|
{
|
||||||
var server = CreateServer(configuration =>
|
var server = CreateServer(new KestrelServerOptions() { ThreadCount = threadCount });
|
||||||
new KestrelServerInformation(configuration)
|
|
||||||
{
|
|
||||||
ThreadCount = threadCount
|
|
||||||
});
|
|
||||||
|
|
||||||
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => StartDummyApplication(server));
|
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => StartDummyApplication(server));
|
||||||
|
|
||||||
|
|
@ -32,11 +29,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StartWithInvalidAddressThrows()
|
public void StartWithInvalidAddressThrows()
|
||||||
{
|
{
|
||||||
var server = CreateServer(configuration =>
|
var addressesFeature = new ServerAddressesFeature();
|
||||||
new KestrelServerInformation(configuration)
|
addressesFeature.Addresses.Add("http:/asdf");
|
||||||
{
|
var server = CreateServer(new KestrelServerOptions(), addressesFeature);
|
||||||
Addresses = {"http:/asdf"}
|
|
||||||
});
|
|
||||||
|
|
||||||
var exception = Assert.Throws<FormatException>(() => StartDummyApplication(server));
|
var exception = Assert.Throws<FormatException>(() => StartDummyApplication(server));
|
||||||
|
|
||||||
|
|
@ -46,32 +41,25 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StartWithEmptyAddressesThrows()
|
public void StartWithEmptyAddressesThrows()
|
||||||
{
|
{
|
||||||
var server = CreateServer(configuration =>
|
var server = CreateServer(new KestrelServerOptions(), new ServerAddressesFeature());
|
||||||
{
|
|
||||||
var information = new KestrelServerInformation(configuration);
|
|
||||||
|
|
||||||
information.Addresses.Clear();
|
|
||||||
|
|
||||||
return information;
|
|
||||||
});
|
|
||||||
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => StartDummyApplication(server));
|
var exception = Assert.Throws<InvalidOperationException>(() => StartDummyApplication(server));
|
||||||
|
|
||||||
Assert.Equal("No recognized listening addresses were configured.", exception.Message);
|
Assert.Equal("No recognized listening addresses were configured.", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KestrelServer CreateServer(Func<IConfiguration, IKestrelServerInformation> serverInformationFactory)
|
private static KestrelServer CreateServer(KestrelServerOptions options, IServerAddressesFeature addressesFeature = null)
|
||||||
{
|
{
|
||||||
var configuration = new ConfigurationBuilder().Build();
|
|
||||||
var information = serverInformationFactory(configuration);
|
|
||||||
|
|
||||||
var features = new FeatureCollection();
|
var features = new FeatureCollection();
|
||||||
features.Set(information);
|
if (addressesFeature != null)
|
||||||
|
{
|
||||||
|
features.Set(addressesFeature);
|
||||||
|
}
|
||||||
|
|
||||||
var lifetime = new LifetimeNotImplemented();
|
var lifetime = new LifetimeNotImplemented();
|
||||||
var logger = new TestApplicationErrorLogger();
|
var logger = new TestApplicationErrorLogger();
|
||||||
|
|
||||||
return new KestrelServer(features, lifetime, logger);
|
return new KestrelServer(features, options, lifetime, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartDummyApplication(IServer server)
|
private static void StartDummyApplication(IServer server)
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
ThreadPool = new LoggingThreadPool(Log);
|
ThreadPool = new LoggingThreadPool(Log);
|
||||||
DateHeaderValueManager = new TestDateHeaderValueManager();
|
DateHeaderValueManager = new TestDateHeaderValueManager();
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder().Build();
|
ServerOptions = new KestrelServerOptions();
|
||||||
ServerInformation = new KestrelServerInformation(configuration);
|
ServerOptions.ShutdownTimeout = TimeSpan.FromSeconds(5);
|
||||||
ServerInformation.ShutdownTimeout = TimeSpan.FromSeconds(5);
|
|
||||||
|
|
||||||
HttpComponentFactory = new HttpComponentFactory(ServerInformation);
|
HttpComponentFactory = new HttpComponentFactory(ServerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestServiceContext(IConnectionFilter filter)
|
public TestServiceContext(IConnectionFilter filter)
|
||||||
: this()
|
: this()
|
||||||
{
|
{
|
||||||
ServerInformation.ConnectionFilter = filter;
|
ServerOptions.ConnectionFilter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestDelegate App
|
public RequestDelegate App
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue