ASP.NET Core 5-Kestrel服务器

文章译自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-5.0

 

Kestrel是ASP.NET Core中的跨平台Web服务器,也是ASP.NET Core项目模板中默认包含的web服务器。

Kestrel支持以下场景:

  • HTTPS
  • 用于启用WebSockets的不透明升级。
  • 在Nginx后面实现高性能的Unix套接字。
  • HTTP/2 (除了在macOS上)
  • HTTP/2将在未来的版本中支持macOS。

.NET Core支持的所有平台和版本都支持Kestrel。

支持HTTP/2

如果满足以下基本要求,HTTP/2可用于ASP.NET Core应用程序。

  • 操作系统:Windows Server 2016/Windows 10或更新版本或使用OpenSSL 1.0.2或更高版本的Linux(例如,Ubuntu 16.04或更高版本)。
  • 目标框架:.NET Core 2.2或更高版本。
  • 应用层协议协商(ALPN)连接
  • TLS 1.2或更高版本的连接

HTTP/2将在未来的版本中支持macOS。Kestrel 对 Windows Server 2012 R2 和 Windows 8.1 上的 HTTP/2 支持有限。支持有限是因为这些操作系统上支持的TLS密码套件列表有限。可能需要使用椭圆曲线数字签名算法 (ECDSA) 生成的证书来保护 TLS 连接。

如果建立了HTTP/2连接,HttpRequest.Protocol会报告给HTTP/2。

默认情况下,HTTP/2被禁用。

何时将Kestrel与反向代理一起使用?

Kestrel可以单独使用,也可以与反向代理服务器一起使用,如互联网信息服务(IIS)、Nginx或Apache。反向代理服务器接收来自网络的HTTP请求并将其转发到Kestrel。

Kestrel被用作边缘(面向互联网)的网络服务器。

ASP.NET Core 5-Kestrel解析

 

Kestrel在反向代理配置中使用。

ASP.NET Core 5-Kestrel解析

 

无论有无反向代理服务器,这两种配置都是支持的主机配置。

作为边缘服务器使用的Kestrel如果没有反向代理服务器,则不支持在多个进程之间共享相同的IP和端口。当Kestrel配置为监听一个端口时,Kestrel会处理该端口的所有流量,无论请求的Host头是什么。可以共享端口的反向代理能够将请求转发到Kestrel的唯一IP和端口上。

即使不需要反向代理服务器,使用反向代理服务器也是一个不错的选择。

反向代理服务器:

  • 可以限制它所托管的应用程序的暴露的公共表面区域。
  • 提供额外的配置和防御层。
  • 可能会与现有的基础设施更好地整合。
  • 简化负载均衡和安全通信(HTTPS)配置。只有反向代理服务器需要X.509证书,该服务器可以使用纯HTTP与内部网络上的应用服务器进行通信。

ASP.NET Core 应用中的Kestrel

ASP.NET Core 项目模板默认在Program.cs使用Kestrel, ConfigureWebHostDefaults 方法调用了UseKestrel:

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}
 
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

ConfigureWebHostDefaults在ConfigureWebDefaults调用UseKestrel方法:

public static IHostBuilder ConfigureWebHostDefaults(
      this IHostBuilder builder,
      Action<IWebHostBuilder> configure)
    {
      if (configure == null)
        throw new ArgumentNullException(nameof (configure));
      return builder.ConfigureWebHost((Action<IWebHostBuilder>) (webHostBuilder =>
      {
        Microsoft.AspNetCore.WebHost.ConfigureWebDefaults(webHostBuilder);
        configure(webHostBuilder);
      }));
}

ConfigureWebDefaults为实际调用UseKestrel的方法:

internal static void ConfigureWebDefaults(IWebHostBuilder builder)
    {
      builder.ConfigureAppConfiguration((Action<WebHostBuilderContext, IConfigurationBuilder>) ((ctx, cb) =>
      {
       ...
      }));
      builder.UseKestrel((Action<WebHostBuilderContext, KestrelServerOptions>) ((builderContext, options) => options.Configure((IConfiguration) builderContext.Configuration.GetSection("Kestrel"), true))).ConfigureServices((Action<WebHostBuilderContext, IServiceCollection>) ((hostingContext, services) =>
      {
        ...
      })).UseIIS().UseIISIntegration();
    }

要在调用ConfigureWebHostDefaults后提供额外的配置,请使用ConfigureKestrel:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(serverOptions =>
            {
                // Set properties and call methods on options
            })
            .UseStartup<Startup>();
        });

Kestrel 配置项

Kestrel Web服务器有约束配置选项,在面向互联网的部署中特别有用。

在KestrelServerOptions类的Limits属性上设置约束。Limits 属性持有 KestrelServerLimits 类的实例。

以下示例使用 Microsoft.AspNetCore.Server.Kestrel.Core 命名空间。

using Microsoft.AspNetCore.Server.Kestrel.Core;

 

在本文后面的例子中,Kestrel选项是在C#代码中配置的。也可以使用配置提供者来设置Kestrel选项。例如,文件配置提供者可以从appsettings.json或appsettings.{Environment}.json文件中加载Kestrel配置。

{
  "Kestrel": {
    "Limits": {
      "MaxConcurrentConnections": 100,
      "MaxConcurrentUpgradedConnections": 100
    },
    "DisableStringReuse": true
  }
}

KestrelServerOptions和端点配置可由配置提供者配置。其余的Kestrel配置必须在C#代码中进行配置。

使用以下方法之一:

  1. 在Startup.ConfigureServices中配置Kestrel。
  2. 在Startup类中注入一个IConfiguration的实例。下面的示例假设注入的配置被分配给 Configuration 属性。

在Startup.ConfigureServices中,将配置的Kestrel部分加载到Kestrel的配置中。

using Microsoft.Extensions.Configuration
 
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
 
    public IConfiguration Configuration { get; }
 
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<KestrelServerOptions>(
            Configuration.GetSection("Kestrel"));
    }
 
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
    }
}

构建主机时配置Kestrel:

在Program.cs中,将Kestrel部分的配置加载到Kestrel的配置中。

// using Microsoft.Extensions.DependencyInjection;
 
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((context, services) =>
        {
            services.Configure<KestrelServerOptions>(
                context.Configuration.GetSection("Kestrel"));
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

前面两种方法都可以与任何配置提供商(ConfigurationProvider)合作。.目前现有的ConfigurationProvider有以下几种

Provider Provides configuration from
Azure Key Vault configuration provider Azure Key Vault
Azure App configuration provider Azure App Configuration
Command-line configuration provider Command-line parameters
Custom configuration provider Custom source
Environment Variables configuration provider Environment variables
File configuration provider INI, JSON, and XML files
Key-per-file configuration provider Directory files
Memory configuration provider In-memory collections
User secrets File in user profiles directories

 

Keep-alive 超时

获取或者设置 keep-alive timeout。默认超时时间为2分钟。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxConcurrentConnections = 100;
    serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
    serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
    serverOptions.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, 
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, 
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001, 
        listenOptions =>
        {
            listenOptions.UseHttps("testCert.pfx", 
                "testPassword");
        });
    serverOptions.Limits.KeepAliveTimeout = 
        TimeSpan.FromMinutes(2);
    serverOptions.Limits.RequestHeadersTimeout = 
        TimeSpan.FromMinutes(1);
}

客户端最大链接数

通过下面的代码可以设置整个应用的最大并发开放TCP连接数。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxConcurrentConnections = 100;
    serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
    serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
    serverOptions.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001,
        listenOptions =>
        {
            listenOptions.UseHttps("testCert.pfx",
                "testPassword");
        });
    serverOptions.Limits.KeepAliveTimeout =
        TimeSpan.FromMinutes(2);
    serverOptions.Limits.RequestHeadersTimeout =
        TimeSpan.FromMinutes(1);
})

对于从HTTP或HTTPS升级到另一种协议的连接有一个单独的限制(例如,在WebSockets请求中)。在连接升级后,它不会被计入最大并发连接数限制。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxConcurrentConnections = 100;
    serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
    serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
    serverOptions.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001,
        listenOptions =>
        {
            listenOptions.UseHttps("testCert.pfx",
                "testPassword");
        });
    serverOptions.Limits.KeepAliveTimeout =
        TimeSpan.FromMinutes(2);
    serverOptions.Limits.RequestHeadersTimeout =
        TimeSpan.FromMinutes(1);
})

默认情况下,最大连接数是无限的(null)。

最大请求体大小

默认在最大请求体大小是 30,000,000 bytes,约为28.6 MB。

在ASP.NET Core MVC应用程序中覆盖限制的推荐方法是使用控制器方法上的RequestSizeLimitAttribute属性:

[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()

下面是一个例子,说明如何在每个请求上为应用配置约束:

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxConcurrentConnections = 100;
    serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
    serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
    serverOptions.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001,
        listenOptions =>
        {
            listenOptions.UseHttps("testCert.pfx",
                "testPassword");
        });
    serverOptions.Limits.KeepAliveTimeout =
        TimeSpan.FromMinutes(2);
    serverOptions.Limits.RequestHeadersTimeout =
        TimeSpan.FromMinutes(1);
})

覆盖中间件中特定请求的设置:

app.Run(async (context) =>
{
    context.Features.Get<IHttpMaxRequestBodySizeFeature>()
        .MaxRequestBodySize = 10 * 1024;
 
    var minRequestRateFeature =
        context.Features.Get<IHttpMinRequestBodyDataRateFeature>();
    var minResponseRateFeature =
        context.Features.Get<IHttpMinResponseDataRateFeature>();
 
    if (minRequestRateFeature != null)
    {
        minRequestRateFeature.MinDataRate = new MinDataRate(
            bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    }
 
    if (minResponseRateFeature != null)
    {
        minResponseRateFeature.MinDataRate = new MinDataRate(
            bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    }

如果应用程序在开始读取请求后才配置请求的限制,就会抛出一个异常。有一个IsReadOnly属性可以指示MaxRequestBodySize属性是否处于只读状态,这意味着配置限制已经太晚了。

当应用程序在ASP.NET核心模块后面的进程外运行时,Kestrel的请求体大小限制会被禁用,因为IIS已经设置了限制。

最低请求体数据率

Kestrel 每秒钟检查数据是否以指定的速率(字节/秒)到达。如果速率降到最低值以下,连接就会超时。宽限期是Kestrel给客户端提高发送速率到最低值的时间,在此期间不检查速率。宽限期有助于避免由于TCP慢启动而导致最初以慢速发送数据的连接被丢弃。

默认的最小速率是240字节/秒,宽限期为5秒。

最低速率也适用于响应。设置请求限制和响应限制的代码是一样的,只是在属性和接口名中有RequestBody或Response。

下面是一个例子,说明如何在Program.cs中配置最小数据速率:

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxConcurrentConnections = 100;
    serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
    serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
    serverOptions.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001,
        listenOptions =>
        {
            listenOptions.UseHttps("testCert.pfx",
                "testPassword");
        });
    serverOptions.Limits.KeepAliveTimeout =
        TimeSpan.FromMinutes(2);
    serverOptions.Limits.RequestHeadersTimeout =
        TimeSpan.FromMinutes(1);
})

覆盖中间件中每个请求的最低速率限:

app.Run(async (context) =>
{
    context.Features.Get<IHttpMaxRequestBodySizeFeature>()
        .MaxRequestBodySize = 10 * 1024;
 
    var minRequestRateFeature =
        context.Features.Get<IHttpMinRequestBodyDataRateFeature>();
    var minResponseRateFeature =
        context.Features.Get<IHttpMinResponseDataRateFeature>();
 
    if (minRequestRateFeature != null)
    {
        minRequestRateFeature.MinDataRate = new MinDataRate(
            bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    }
 
    if (minResponseRateFeature != null)
    {
        minResponseRateFeature.MinDataRate = new MinDataRate(
            bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    }

前面示例中提到的IHttpMinResponseDataRateFeature在HTTP/2请求的HttpContext.Features中是不存在的,因为由于协议对请求复用的支持,HTTP/2一般不支持在每次请求的基础上修改速率限制。但是,IHttpMinRequestBodyDataRateFeature仍然存在于HTTP/2请求的HttpContext.Features中,因为即使对于HTTP/2请求来说,仍然可以通过将IHttpMinRequestBodyDataRateFeature.MinDataRate设置为null来完全在每个请求的基础上禁用读取速率限制。试图读取IHttpMinRequestBodyDataRateFeature.MinDataRate或试图将其设置为null以外的值,将导致一个NotSupportedException被抛出,给定一个HTTP/2请求。

通过KestrelServerOptions.Limits配置的服务器范围的速率限制仍然适用于HTTP/1.x和HTTP/2连接。

请求头超时

获取或设置服务器接收请求头RequestHeadersTimeout的最大时间。默认值为30秒。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxConcurrentConnections = 100;
    serverOptions.Limits.MaxConcurrentUpgradedConnections = 100;
    serverOptions.Limits.MaxRequestBodySize = 10 * 1024;
    serverOptions.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100,
            gracePeriod: TimeSpan.FromSeconds(10));
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001,
        listenOptions =>
        {
            listenOptions.UseHttps("testCert.pfx",
                "testPassword");
        });
    serverOptions.Limits.KeepAliveTimeout =
        TimeSpan.FromMinutes(2);
    serverOptions.Limits.RequestHeadersTimeout =
        TimeSpan.FromMinutes(1);
})

每个连接的最大流量

Http2.MaxStreamsPerConnection限制每个HTTP/2连接的并发请求流的数量。多余的流会被拒绝。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.Http2.MaxStreamsPerConnection = 100;
});

默认值为 100。

请求表头大小

HPACK解码器对HTTP/2连接的HTTP头进行解压。Http2.HeaderTableSize限制了HPACK解码器使用的头压缩表的大小。该值以八位数为单位提供,必须大于零(0)。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.Http2.HeaderTableSize = 4096;
});

默认值是4096。

最大帧大小

Http2.MaxFrameSize 表示服务器接收或发送的 HTTP/2 连接帧有效负载的最大允许大小。 该值以八位字节提供,必须介于 2^14 (16,384) 和 2^24-1 (16,777,215) 之间。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.Http2.MaxFrameSize = 16384;
});

默认值为 2^14 (16,384)。

最大请求头大小

Http2.MaxRequestHeaderFieldSize 表示请求标头值的允许的最大大小(用八进制表示)。 此限制适用于名称和值的压缩和未压缩表示形式。 该值必须大于零 (0)。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.Http2.MaxRequestHeaderFieldSize = 8192;
});

默认值是 8192。

初始连接窗口大小

Http2.InitialConnectionWindowSize表示服务器在每个连接的所有请求(流)中一次汇总的最大请求体数据的字节数。请求也受到Http2.InitialStreamWindowSize的限制。该值必须大于或等于65,535且小于2^31(2,147,483,648)。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.Http2.InitialConnectionWindowSize = 131072;
});

默认值是128 KB (131,072)。

初始流窗口大小

Http2.InitialStreamWindowSize表示服务器在每次请求(流)时缓冲区的最大请求体数据,单位为字节。请求也受到Http2.InitialConnectionWindowSize的限制。该值必须大于或等于65,535且小于2^31(2,147,483,648)。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.Http2.InitialStreamWindowSize = 98304;
});

初始值是96 KB (98,304)。

HTTP/2 keep alive ping配置

Kestrel可以配置为向连接的客户端发送HTTP/2 ping。HTTP/2 ping有多种用途。

  • 保持闲置连接的活力。一些客户端和代理服务器会关闭闲置的连接。HTTP/2 ping被认为是连接上的活动,防止连接被当作空闲关闭。
  • 关闭不健康的连接。客户端在配置的时间内没有响应keep alive ping的连接会被服务器关闭。
  • 有两个配置选项与HTTP/2 keep alive ping有关。
  • Http2.KeepAlivePingInterval是一个TimeSpan,用于配置内部ping。如果服务器在这段时间内没有收到任何帧,就会向客户端发送keep alive ping。当这个选项设置为TimeSpan.MaxValue时,Keep alive ping将被禁用。默认值是TimeSpan.MaxValue。
  • Http2.KeepAlivePingTimeout是一个配置ping超时的TimeSpan。如果服务器在这个超时内没有收到任何帧,比如响应ping,那么连接就会被关闭。当这个选项设置为TimeSpan.MaxValue时,Keep alive超时被禁用。默认值是20秒。
webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.Http2.KeepAlivePingInterval = TimeSpan.FromSeconds(30);
    serverOptions.Limits.Http2.KeepAlivePingTimeout = TimeSpan.FromSeconds(60);
});

预告片

HTTP Trailers类似于HTTP Headers,只是它们是在响应体发送后发送的。对于IIS和HTTP.SYS,只支持HTTP/2响应跟踪。

if (httpContext.Response.SupportsTrailers())
{
    httpContext.Response.DeclareTrailer("trailername");
    // Write body
    httpContext.Response.WriteAsync("Hello world");
    httpContext.Response.AppendTrailer("trailername", "TrailerValue");
}

在前面的示例代码中:

  • SupportsTrailers确保响应支持拖车。
  • DeclareTrailer将给定的拖车名称添加到拖车响应头中。声明响应的预告片是可选的,但建议使用。如果DeclareTrailer被调用,它必须在响应头被发送之前。
  • AppendTrailer附加拖车。

重置

重置允许服务器用指定的错误代码重置HTTP/2请求。重置请求被视为中止。

var resetFeature = httpContext.Features.Get<IHttpResetFeature>();
resetFeature.Reset(errorCode: 2);

前面代码示例中的Reset指定了INTERNAL_ERROR错误代码。有关HTTP/2错误代码的更多信息,请访问HTTP/2规范错误代码部分。

异步 I/O

AllowSynchronousIO控制是否允许请求和响应的同步I/O。默认值为false。

警告

大量阻塞的同步I/O操作可能会导致线程池饥饿,从而使应用程序无法响应。只有在使用不支持异步I/O的库时,才启用AllowSynchronousIO。

下面的例子可以实现同步I/O。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.AllowSynchronousIO = true;
})

有关其他Kestrel选项和限制的信息,请参见。

  • KestrelServerOptions
  • KestrelServerLimits
  • ListenOptions

终结点配置

默认情况下,ASP.NET Core会绑定到。

  • http://localhost:5000
  • https://localhost:5001(存在本地开发证书时)。

使用环境变量:

  • ASPNETCORE_URLS环境变量来指定URL。
  • --urls 命令行参数。
  • urls 主机配置键。
  • UseUrls扩展方法。

使用这些方法提供的值可以是一个或多个HTTP和HTTPS端点(如果有默认的证书,则为HTTPS)。将值配置为分号分隔的列表(例如,"Urls":"http://localhost:8000;http://localhost:8001")。

以下情况会创建一个开发证书。

  • 安装.NET Core SDK时。
  • 使用dev-certs工具来创建证书。

某些浏览器需要授予明确的权限才能信任本地开发证书。

项目模板将应用程序配置为默认在HTTPS上运行,并包括HTTPS重定向和HSTS支持。

调用KestrelServerOptions上的Listen或ListenUnixSocket方法,为Kestrel配置URL前缀和端口。

UseUrls、--urls命令行参数、urls主机配置密钥和ASPNETCORE_URLS环境变量也可以使用,但有本节后面提到的限制(HTTPS端点配置必须有默认证书)。

KestrelServerOptions 配置:

ConfigureEndpointDefaults(Action<ListenOptions>)

为每个指定的端点指定一个要运行的配置Action。多次调用ConfigureEndpointDefaults会用最后指定的Action替换之前的Action。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureEndpointDefaults(listenOptions =>
    {
        // Configure endpoint defaults
    });
});

配置(IConfiguration)

创建一个配置加载器,用于设置Kestrel,它将一个IConfiguration作为输入。该配置必须限定在Kestrel的配置部分。

ListenOptions.UseHttps

配置Kestrel使用HTTPS。

ListenOptions.UseHttps扩展。

UseHttps:配置Kestrel使用默认证书的HTTPS。如果没有配置默认证书,则抛出一个异常。
UseHttps(string fileName)
UseHttps(string fileName, string password)
UseHttps(string fileName, string password, Action<HttpsConnectionAdapterOptions> configureOptions)
UseHttps(StoreName,subject)
UseHttps(StoreName, string subject, bool allowInvalid)
UseHttps(StoreName, string subject, bool allowInvalid, StoreLocation location)
UseHttps(StoreName, string subject, bool allowInvalid, StoreLocation location, Action<HttpsConnectionAdapterOptions> configureOptions)
UseHttps(X509Certificate2 serverCertificate)
UseHttps(X509Certificate2 serverCertificate, Action<HttpsConnectionAdapterOptions> configureOptions)
UseHttps(Action<HttpsConnectionAdapterOptions> configureOptions)

ListenOptions.UseHttps参数。

filename是证书文件的路径和文件名,相对于包含应用程序内容文件的目录。
password是访问X.509证书数据所需的密码。
configureOptions是一个配置HttpsConnectionAdapterOptions的Action。返回ListenOptions。
storeName是要从哪个证书商店加载证书。
subject是证书的主体名称。
allowInvalid表示是否应该考虑无效证书,例如自签名证书。
location 是要加载证书的存储位置。
serverCertificate 是 X.509 证书。

在生产中,必须明确配置HTTPS。至少,必须提供一个默认证书。

下面描述了支持的配置。

  • 无配置
  • 替换配置中的默认证书
  • 更改代码中的默认值

无配置

Kestrel 在 http://localhost:5000 和 https://localhost:5001 上监听(如果有默认的证书)。

替换配置中的默认证书

CreateDefaultBuilder默认调用Configure(context.Configuration.GetSection("Kestrel"))来加载Kestrel配置。Kestrel有一个默认的HTTPS应用设置配置模式。配置多个端点,包括URL和要使用的证书,可以从磁盘上的文件或从证书存储中配置。

在下面的appsettings.json例子中:

  • 将AllowInvalid设置为true,允许使用无效证书(例如,自签名证书)。
  • 任何没有指定证书的HTTPS端点(在下面的例子中是HttpsDefaultCert)都会回到证书>默认证书或开发证书下定义的证书。
{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:5000"
      },
      "HttpsInlineCertFile": {
        "Url": "https://localhost:5001",
        "Certificate": {
          "Path": "<path to .pfx file>",
          "Password": "<certificate password>"
        }
      },
      "HttpsInlineCertStore": {
        "Url": "https://localhost:5002",
        "Certificate": {
          "Subject": "<subject; required>",
          "Store": "<certificate store; required>",
          "Location": "<location; defaults to CurrentUser>",
          "AllowInvalid": "<true or false; defaults to false>"
        }
      },
      "HttpsDefaultCert": {
        "Url": "https://localhost:5003"
      },
      "Https": {
        "Url": "https://*:5004",
        "Certificate": {
          "Path": "<path to .pfx file>",
          "Password": "<certificate password>"
        }
      }
    },
    "Certificates": {
      "Default": {
        "Path": "<path to .pfx file>",
        "Password": "<certificate password>"
      }
    }
  }
}

对任何证书节点使用路径和密码的替代方法是使用证书存储字段指定证书。例如,证书 > 缺省证书可以指定为:

"Default": {
  "Subject": "<subject; required>",
  "Store": "<cert store; required>",
  "Location": "<location; defaults to CurrentUser>",
  "AllowInvalid": "<true or false; defaults to false>"
}

模式说明。

  • 端点名称不区分大小写。例如,HTTPS和Https是有效的。
  • 每个端点都需要Url参数。该参数的格式与顶层Url配置参数相同,只是它只限于一个值。
  • 这些端点取代了顶层Urls配置中定义的端点,而不是添加到这些端点中。通过Listen在代码中定义的端点与配置部分中定义的端点是累加的。
  • 证书部分是可选的。如果没有指定证书部分,则会使用前面方案中定义的默认值。如果没有可用的默认值,服务器会抛出一个异常并无法启动。
  • 证书部分支持路径-密码和主题-存储证书。
  • 只要不引起端口冲突,可以用这种方式定义任意数量的端点。
  • options.Configure(context.Configuration.GetSection("{SECTION}"))返回一个带有.Endpoint(string name, listenOptions => { })方法的KestrelConfigurationLoader,该方法可用于补充配置端点的设置。
webBuilder.UseKestrel((context, serverOptions) =>
{
    serverOptions.Configure(context.Configuration.GetSection("Kestrel"))
        .Endpoint("HTTPS", listenOptions =>
        {
            listenOptions.HttpsOptions.SslProtocols = SslProtocols.Tls12;
        });
});

可以直接访问KestrelServerOptions.ConfigurationLoader,以继续迭代现有的加载器,如CreateDefaultBuilder提供的加载器。

  • 每个端点的配置部分可以在Endpoint方法中的选项上获得,这样就可以读取自定义设置。
  • 可以通过再次调用options.Configure(context.Configuration.GetSection("{SECTION}"))与另一个部分来加载多个配置。只有最后一个配置才会被使用,除非在之前的实例上明确调用Load。元包没有调用Load,所以它的默认配置部分可能被替换。
  • KestrelConfigurationLoader将KestrelServerOptions中的Listen系列API镜像为Endpoint重载,因此代码和配置端点可以在同一个地方进行配置。这些重载不使用名称,只消耗配置中的默认设置。

更改代码中的默认值

ConfigureEndpointDefaults和ConfigureHttpsDefaults可用于更改ListenOptions和HttpsConnectionAdapterOptions的默认设置,包括覆盖之前方案中指定的默认证书。ConfigureEndpointDefaults和ConfigureHttpsDefaults应该在配置任何端点之前被调用。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureEndpointDefaults(listenOptions =>
    {
        // Configure endpoint defaults
    });
    serverOptions.ConfigureHttpsDefaults(listenOptions =>
    {
        listenOptions.SslProtocols = SslProtocols.Tls12;
    });
});

Kestrel support for SNI

服务器名称指示(SNI)可用于在同一IP地址和端口上托管多个域。为了使SNI发挥作用,客户端在TLS握手过程中向服务器发送安全会话的主机名,以便服务器能够提供正确的证书。在TLS握手后的安全会话期间,客户端使用提供的证书与服务器进行加密通信。

Kestrel通过ServerCertificateSelector回调支持SNI。该回调在每个连接中被调用一次,以允许应用程序检查主机名并选择适当的证书。

SNI支持需要。

  • 运行在目标框架netcoreapp2.1或更高版本上。在net461或更高版本上,回调被调用,但名称总是空的。如果客户端没有在TLS握手中提供主机名参数,名称也是空的。
  • 所有网站都运行在同一个Kestrel实例上。Kestrel不支持在没有反向代理的情况下在多个实例之间共享IP地址和端口。
webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenAnyIP(5005, listenOptions =>
    {
        listenOptions.UseHttps(httpsOptions =>
        {
            var localhostCert = CertificateLoader.LoadFromStoreCert(
                "localhost", "My", StoreLocation.CurrentUser,
                allowInvalid: true);
            var exampleCert = CertificateLoader.LoadFromStoreCert(
                "example.com", "My", StoreLocation.CurrentUser,
                allowInvalid: true);
            var subExampleCert = CertificateLoader.LoadFromStoreCert(
                "sub.example.com", "My", StoreLocation.CurrentUser,
                allowInvalid: true);
            var certs = new Dictionary<string, X509Certificate2>(
                StringComparer.OrdinalIgnoreCase);
            certs["localhost"] = localhostCert;
            certs["example.com"] = exampleCert;
            certs["sub.example.com"] = subExampleCert;
            httpsOptions.ServerCertificateSelector = (connectionContext, name) =>
            {
                if (name != null && certs.TryGetValue(name, out var cert))
                {
                    return cert;
                }
                return exampleCert;
            };
        });
    });
});

连接记录

调用UseConnectionLogging为连接上的字节级通信发出Debug级别的日志。连接日志对于排除低级通信中的问题很有帮助,例如在TLS加密期间和代理服务器后面。如果将UseConnectionLogging放在UseHttps之前,则会记录加密流量。如果UseConnectionLogging放置在UseHttps之后,则会记录解密流量。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 8000, listenOptions =>
    {
        listenOptions.UseConnectionLogging();
    });
});

绑定到一个TCP套接字

Listen方法与TCP套接字绑定,选项lambda允许配置X.509证书。

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(serverOptions =>
            {
                serverOptions.Listen(IPAddress.Loopback, 5000);
                serverOptions.Listen(IPAddress.Loopback, 5001, 
                    listenOptions =>
                    {
                        listenOptions.UseHttps("testCert.pfx", 
                            "testPassword");
                    });
            })
            .UseStartup<Startup>();
        });

该示例通过ListenOptions为端点配置HTTPS。使用相同的 API 为特定端点配置其他 Kestrel 设置。

在 Windows 上,可以使用 New-SelfignedCertificate PowerShell cmdlet 创建自签名证书。有关不支持的示例,请参见 UpdateIISExpressSSLForChrome.ps1。

在 macOS、Linux 和 Windows 上,可以使用 OpenSSL 创建证书。

绑定到Unix套接字

在Unix套接字上使用ListenUnixSocket进行监听,以提高Nginx的性能,如本例所示。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenUnixSocket("/tmp/kestrel-test.sock");
    serverOptions.ListenUnixSocket("/tmp/kestrel-test.sock", 
        listenOptions =>
        {
            listenOptions.UseHttps("testCert.pfx", 
                "testpassword");
        });
})

端口0

当指定端口号为0时,Kestrel会动态地绑定到一个可用的端口。下面的例子显示了如何在运行时确定Kestrel实际绑定的端口。

public void Configure(IApplicationBuilder app)
{
    var serverAddressesFeature = 
        app.ServerFeatures.Get<IServerAddressesFeature>();
    app.UseStaticFiles();
    app.Run(async (context) =>
    {
        context.Response.ContentType = "text/html";
        await context.Response
            .WriteAsync("<!DOCTYPE html><html lang=\"en\"><head>" +
                "<title></title></head><body><p>Hosted by Kestrel</p>");
        if (serverAddressesFeature != null)
        {
            await context.Response
                .WriteAsync("<p>Listening on the following addresses: " +
                    string.Join(", ", serverAddressesFeature.Addresses) +
                    "</p>");
        }
        await context.Response.WriteAsync("<p>Request URL: " +
            $"{context.Request.GetDisplayUrl()}<p>");
    });
}

当应用程序运行时,控制台窗口输出显示应用程序可以到达的动态端口。

Listening on the following addresses: http://127.0.0.1:48508

局限性

用以下方法配置端点。

  • UseUrls
  • --urls命令行参数
  • urls主机配置键
  • ASPNETCORE_URLS环境变量

这些方法对于使代码在Kestrel以外的服务器上工作很有用。但是,请注意以下限制。

  • 除非在HTTPS端点配置中提供了默认证书(例如,使用KestrelServerOptions配置或本主题前面所示的配置文件),否则不能使用这些方法来使用HTTPS。
  • 当同时使用Listen和UseUrls方法时,Listen端点会覆盖UseUrls端点。

IIS 终结点配置

当使用 IIS 时,IIS 覆盖绑定的 URL 绑定是由 Listen 或 UseUrls 设置的。更多信息,请参阅ASP.NET核心模块主题。

ListenOptions.Protocols

Protocols 属性建立了在连接端点上或为服务器启用的 HTTP 协议(HttpProtocols)。从HttpProtocols枚举中为Protocols属性赋值。

HttpProtocols enum value Connection protocol permitted


Http1 仅限HTTP/1.1。可以使用或不使用TLS.

Http2 仅限HTTP/2。只有在客户端支持先验知识模式的情况下,可以不使用TLS。

Http1AndHttp2 HTTP/1.1和HTTP/2。HTTP/2要求客户端在TLS应用层协议协商(ALPN)握手中选择HTTP/2,否则,连接默认为HTTP/1.1。

任何端点的默认ListenOptions.Protocols值是HttpProtocols.Http1AndHttp2。

HTTP/2的TLS限制。

  • TLS版本1.2或更高
  • 重新谈判失效
  • 禁用压缩
  • 最小历时密钥交换大小。
  • 椭圆曲线Diffie-Hellman(ECDHE)[RFC4492]:最小224位。
  • 有限场Diffie-Hellman(DHE)[TLS12]:最小2048位。
  • 密码套件不被禁止。

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256[TLS-ECDHE]与P-256椭圆曲线[FIPS186]是默认支持的。

下面的例子允许在8000端口上进行HTTP/1.1和HTTP/2连接。连接由TLS提供的证书来保护。

webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 8000, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});

如果需要,使用连接中间件在每个连接的基础上过滤特定密码的TLS握手。

下面的例子对于任何应用程序不支持的密码算法都会抛出NotSupportedException。另外,定义ITlsHandshakeFeature.CipherAlgorithm并将其与可接受的密码套件列表进行比较。

不使用CipherAlgorithmType.Null密码算法进行加密。

// using System.Net;
// using Microsoft.AspNetCore.Connections;
webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 8000, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
        listenOptions.UseTlsFilter();
    });
});
using System;
using System.Security.Authentication;
using Microsoft.AspNetCore.Connections.Features;
namespace Microsoft.AspNetCore.Connections
{
    public static class TlsFilterConnectionMiddlewareExtensions
    {
        public static IConnectionBuilder UseTlsFilter(
            this IConnectionBuilder builder)
        {
            return builder.Use((connection, next) =>
            {
                var tlsFeature = connection.Features.Get<ITlsHandshakeFeature>();
                if (tlsFeature.CipherAlgorithm == CipherAlgorithmType.Null)
                {
                    throw new NotSupportedException("Prohibited cipher: " +
                        tlsFeature.CipherAlgorithm);
                }
                return next();
            });
        }
    }
}

连接过滤也可以通过IConnectionBuilder lambda进行配置。

// using System;
// using System.Net;
// using System.Security.Authentication;
// using Microsoft.AspNetCore.Connections;
// using Microsoft.AspNetCore.Connections.Features;
webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 8000, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
        listenOptions.Use((context, next) =>
        {
            var tlsFeature = context.Features.Get<ITlsHandshakeFeature>();
            if (tlsFeature.CipherAlgorithm == CipherAlgorithmType.Null)
            {
                throw new NotSupportedException(
                    $"Prohibited cipher: {tlsFeature.CipherAlgorithm}");
            }
            return next();
        });
    });
});

在Linux上,CipherSuitesPolicy可以用来过滤每个连接的TLS握手。

// using System.Net.Security;
// using Microsoft.AspNetCore.Hosting;
// using Microsoft.AspNetCore.Server.Kestrel.Core;
// using Microsoft.Extensions.DependencyInjection;
// using Microsoft.Extensions.Hosting;
webBuilder.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureHttpsDefaults(listenOptions =>
    {
        listenOptions.OnAuthenticate = (context, sslOptions) =>
        {
            sslOptions.CipherSuitesPolicy = new CipherSuitesPolicy(
                new[]
                {
                    TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                    TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
                    // ...
                });
        };
    });
});

从配置中设置协议

CreateDefaultBuilder默认调用serverOptions.Configure(context.Configuration.GetSection("Kestrel"))来加载Kestrel配置。

下面的appsettings.json示例将HTTP/1.1建立为所有端点的默认连接协议。

{
  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http1"
    }
  }
}

下面的appsettings.json示例为一个特定的端点建立了HTTP/1.1连接协议。

{
  "Kestrel": {
    "Endpoints": {
      "HttpsDefaultCert": {
        "Url": "https://localhost:5001",
        "Protocols": "Http1"
      }
    }
  }
}

代码中指定的协议覆盖了配置设置的值。

运输配置

对于需要使用Libuv(UseLibuv)的项目。

将Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv包的依赖关系添加到应用程序的项目文件中。

<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="{VERSION}" />

在IWebHostBuilder上调用UseLibuv。

public class Program
{
     public static void Main(string[] args)
     {
         CreateHostBuilder(args).Build().Run();
     }
     public static IHostBuilder CreateHostBuilder(string[] args) =>
         Host.CreateDefaultBuilder(args)
             .ConfigureWebHostDefaults(webBuilder =>
             {
                 webBuilder.UseLibuv();
                 webBuilder.UseStartup<Startup>();
             });
}

URL 前缀

当使用UseUrls、--urls命令行参数、urls主机配置键或ASPNETCORE_URLS环境变量时,URL前缀可以是以下任何一种格式。

只有HTTP URL前缀有效。当使用UseUrls配置URL绑定时,Kestrel不支持HTTPS。

IPv4地址与端口号

http://65.55.39.10:80/

0.0.0.0是一种特殊情况,可以绑定所有IPv4地址。

IPv6地址与端口号

http://[0:0:0:0:0:ffff:4137:270a]:80/

[::] is the IPv6 和 IPv4 0.0.0.0 相等

主机名称与端口号

http://contoso.com:80/
http://*:80/

主机名、*和+,并不特殊。任何未被识别为有效的IP地址或localhost的东西都会绑定到所有IPv4和IPv6 IP上。要将不同的主机名绑定到同一端口上的不同ASP.NET Core应用程序,请使用HTTP.sys或反向代理服务器,如IIS、Nginx或Apache。

带端口号的主机本地主机名或带端口号的环回IP。

http://localhost:5000/
http://127.0.0.1:5000/
http://[::1]:5000/

当指定localhost时,Kestrel会尝试绑定到IPv4和IPv6环回接口。如果请求的端口被任何一个环回接口上的另一个服务使用,Kestrel将无法启动。如果任何一个环回接口由于任何其他原因不可用(最常见的原因是不支持 IPv6),Kestrel 会记录一个警告。

主机过滤

虽然Kestrel支持基于前缀的配置,如http://example.com:5000,但Kestrel基本上忽略了主机名。主机localhost是一种特殊情况,用于绑定环回地址。除了显式IP地址以外的任何主机都可以绑定到所有公共IP地址。主机头不会被验证。

作为一种变通方法,使用主机过滤中间件。主机过滤中间件由Microsoft.AspNetCore.HostFiltering包提供,该包隐式地提供给ASP.NET Core应用程序。中间件由CreateDefaultBuilder添加,它调用AddHostFiltering。

  • 客户端可能已经发送了部分POST数据。
  • 服务器写入301响应。
  • 在上一个请求体的POST数据被完全读取之前,该连接不能用于新的请求。
  • Kestrel尝试排空请求体。排空请求体意味着读取并丢弃数据而不进行处理。

排水过程在允许连接被重复使用和排出任何剩余数据所需的时间之间做出了权衡。

  • 排除有5秒的超时时间,这是不可以配置的。
  • 如果Content-Length或Transfer-Encoding头指定的所有数据在超时前没有被读取,连接就会被关闭。

有时,你可能想在写入响应之前或之后立即终止请求。例如,客户端可能有限制性的数据上限,所以限制上传数据可能是一个优先事项。在这种情况下要终止请求,可以从控制器、Razor Page或中间件调用HttpContext.Abort。

  • 调用Abort是有注意事项的。
  • 创建新的连接会很慢而且很昂贵
  • 不能保证客户端在连接关闭之前已经读取了响应。
  • 调用Abort应该是罕见的,并保留给严重的错误情况,而不是普通的错误。
  • 只有在需要解决特定问题时才调用Abort。例如,如果恶意客户端试图POST数据,或者客户端代码存在bug导致大量或众多请求时,就调用Abort。
  • 不要调用Abort来处理常见的错误情况,比如HTTP 404(Not Found)。

在调用Abort之前调用HttpResponse.CompleteAsync可以确保服务器已经完成了响应的编写。然而,客户端的行为是不可预测的,他们可能在连接被中止之前没有读取响应。

这个过程对于HTTP/2来说是不同的,因为该协议支持在不关闭连接的情况下中止单个请求流。五秒的耗尽超时并不适用。如果在完成响应后有任何未读的请求体数据,那么服务器会发送一个HTTP/2 RST帧。额外的请求体数据帧会被忽略。

如果可能的话,客户端最好利用Expect:100-continue请求头,等待服务器响应后再开始发送请求体。这样客户端就有机会检查响应,并在发送不需要的数据之前中止。

 

posted on 2020-11-26 10:12  大铭鼎鼎  阅读(2838)  评论(0)    收藏  举报