写在前面
ASP.NET Core 的 web 服务器默认采用 Kestrel, 这是一个基于 libuv(一个跨平台的基于 Node.JS 异步 I/O 库)的跨平台, 轻量级的 Web 服务器.
在开始之前, 先回顾一下. NET Core 3.0 默认的 main()方法模板中, 我们会调用 Host.CreateDefaultBuilder 方法, 该方法的主要功能是配置应用主机及设置主机的属性, 设置 Kestrel 服务器配置为 Web 服务器, 另外还包括日志功能, 应用配置加载等等, 此处不做展开.
作为一个轻量级的 Web Server, 它并没有 IIS,Apache 那些大而全的功能, 但它依然可以单独运行, 也可以搭配 IIS,Apache 等反向代理服务器结合使用.
本文将从源码角度讨论 ASP.NET Core 应用在 Kestrel 的相关知识点.
Kestrel
Kestrel 的存在意义
了解这个问题, 首先需要强调的是. NET Core 应用的目标就是跨平台, 既然要跨平台那么就需要适用各个平台上的 Web 服务器, 各个服务器的启动, 配置等等都是不尽相同的, 如果每个服务器提供一套实现出来, 如果未来出现了一个新的 Web Server, 然后又要增加新的实现, 这会导致. NET Core 应用的适用性滞后, 也会很消耗人力, 无法很好的达到跨平台的目标.
我们可以把 Kestrel 视作一个中间件, 一个适配的功能, 它抽象了各个服务器的特性, 使得各个应用只需要调用同样的接口, 即可最大限度的在各个平台上运行.
运行方式
.NET Core 3.0 下, Kestrel 的集成已经相当成熟了, 也提供了相应的自定义配置, 以使得 Kestrel 的使用更加具有灵活性和可配性. 它可以独立运行, 也可以与反向代理服务器结合使用.
Kestrel 本身是不支持多个应用共享同一个端口的, 但是我们可以通过反向代理服务器来实现统一对外的相同的端口的共享.
以下是其单独运行示意图:
以下是其结合反向代理使用示意图:
Microsoft.AspNetCore.Server.Kestrel.Core
该类库是 Kestrel 的核心类库, 里面包含了该功能的多个逻辑实现, 以下简称改类库为 Kestrel.Core.
Kestrel 适配逻辑
如前文所说, Kestrel 起到了抽象服务器的功能, 那么在适配其他服务器的过程中, 必然涉及到的是, 输入, 输出, 数据交互方式以及 Trace 功能. 在 Kestrel.Core 中, 该功能主要由 AdaptedPipeline 类来实现, 该类继承自 IDuplexPipe, 并通过构造函数获取到了 Pipe 对象. IDuplexPipe 和 Pipe 均位于 System.IO.Pipelines 命名空间下, 详细信息可以点击查看.
AdaptedPipeline 有两个公共方法:
RunAsync(): 用于读取 (读取后会有 Flush 操作) 和写入数据, 并分别装载到 Task 中
CompleteAsync(): 完成读取和写入操作, 并取消基础流的读取
另外还包括四个公共属性, 如下所示:
- public RawStream TransportStream { get; }
- public Pipe Input { get; }
- public Pipe Output { get; }
- public IKestrelTrace Log { get; }
它定义了可从中读取并写入数据的双工管道的对象. IDuplexPipe 有两个属性, System.IO.Pipelines.PipeReader Input { get; }和 System.IO.Pipelines.PipeReader Output { get; }.AdaptedPipeline 还通过构造函数获取到了 Pipe 对象.
RawStream 类继承自 Stream, 并重写了 Stream 的关键属性及方法, 主要目标是提供适合于 Kestrel 读写数据方式的内部封装.
LoggingStream 类也同样继承自 Stream, 和 RawStream 不同的是, 里面增加操作过程的日志记录, 主要用于记录在连接适配过程中的信息, 不过需要启用日志才能把日志信息记录下来, 以下是其对外的使用方式:
- public static class ListenOptionsConnectionLoggingExtensions
- {
- /// <summary>
- /// Emits verbose logs for bytes read from and written to the connection.
- /// </summary>
- /// <returns>
- /// The <see cref="ListenOptions"/>.
- /// </returns>
- public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions)
- {
- return listenOptions.UseConnectionLogging(loggerName: null);
- } 13:
- /// <summary>
- /// Emits verbose logs for bytes read from and written to the connection.
- /// </summary>
- /// <returns>
- /// The <see cref="ListenOptions"/>.
- /// </returns>
- public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions, string loggerName)
- {
- var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
- var logger = loggerName == null ? loggerFactory.CreateLogger<LoggingConnectionAdapter>() : loggerFactory.CreateLogger(loggerName);
- listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(logger));
- return listenOptions;
- } 27: }
Kestrel 特性抽象
该模块下的 Kestrel 特性, 比较重要的有连接超时设置(包括设置超时时间, 重置超时时间以及取消超时限制. 这个特性使得我们的连接变得更加可控, 比如, 在某些特殊场景下, 特殊条件下, 我们需要取消超时限制或者动态重置超时时间),TLS 应用程序协议功能, 基于 Http2.0 的 StreamId 记录功能, 用于停止连接计数的功能.
以下是连接超时接口的源代码:
- /// <summary>
- /// Feature for efficiently handling connection timeouts.
- /// </summary>
- public interface IConnectionTimeoutFeature
- {
- /// <summary>
- /// Close the connection after the specified positive finite <see cref="TimeSpan"/>
- /// unless the timeout is canceled or reset. This will fail if there is an ongoing timeout.
- /// </summary>
- void SetTimeout(TimeSpan timeSpan);
- /// <summary>
- /// Close the connection after the specified positive finite <see cref="TimeSpan"/>
- /// unless the timeout is canceled or reset. This will cancel any ongoing timeouts.
- /// </summary>
- void ResetTimeout(TimeSpan timeSpan);
- /// <summary>
- /// Prevent the connection from closing after a timeout specified by <see cref="SetTimeout(TimeSpan)"/>
- /// or <see cref="ResetTimeout(TimeSpan)"/>.
- /// </summary>
- void CancelTimeout();
- }
Kestrel 选项及限制功能
Kestrel 的选项控制包括监听, Kestrel 服务器, HTTPS 连接适配.
1, 监听选项功能在 ListenOptions 中实现, 该类继承自 IConnectionBuilder,ListenOptions 的主要作用是描述 Kestrel 中已经打开的套接字, 包括 Unix 域套接字路径, 文件描述符, ipendpoint.ListenOptions 内部会维护一个只读的 List<Func<ConnectionDelegate, ConnectionDelegate>>()对象, 并通过 Use()方法加载新的 Func<ConnectionDelegate, ConnectionDelegate > 对象, 然后通过 Build 方式返回最后加入的 Func<ConnectionDelegate, ConnectionDelegate 对象, 源码如下所示:
- 1: public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
- 2: {
- 3: _middleware.Add(middleware);
- 4: return this;
- 5:
- } 6:
- 7: public ConnectionDelegate Build()
- 8: {
- 9: ConnectionDelegate App = context =>
- 10: {
- 11: return Task.CompletedTask;
- 12:
- }; 13:
- 14: for (int i = _middleware.Count - 1; i>= 0; i--)
- 15: {
- 16: var component = _middleware[i];
- 17: App = component(App);
- 18:
- } 19:
- 20: return App;
- 21:
- }
需要注意的是 ListenOptions 在该类库内部还有两个子类, AnyIPListenOptions 和 LocalhostListenOptions, 以用于特定场景的监听使用.
2,Kestrel 服务器选项是在 KestrelServerOptions 中实现的, 该类用于提供 Kestrel 特定功能的编程级别配置, 该类内部会维护 ListenOptions 的列表对象, 该类将 ListenOptions 的功能进一步展开, 并加入了 HTTPS, 证书的默认配置与应用, 这个类比较大, 本文就不贴出源码了, 有兴趣的同学可以自己去翻阅.
3,HTTPS 连接适配选项在 HttpsConnectionAdapterOptions 实现, 这个类用于设置 Kestrel 如何处理 HTTPS 连接, 这里引入和证书功能, SSL 协议, HTTP 协议, 超时功能, 同时这里还可以自定义 HTTPS 连接的时候的证书处理模式(AllowCertificate,RequireCertificate 等), 以下是 HttpsConnectionAdapterOptions 的构造函数:
- public HttpsConnectionAdapterOptions()
- {
- ClientCertificateMode = ClientCertificateMode.NoCertificate;
- SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
- HandshakeTimeout = TimeSpan.FromSeconds(10);
- }
可以看到, 在默认情况下, 是无证书模式, 其 SSL 协议包括 Tls12 和 Tls11 以及指定允许进行 TLS/SSL 握手的最大时间是十秒钟.
4,Kestrel 的限制功能在 KestrelServerLimits 实现, 主要包括:
保持活动状态超时
客户端最大连接数(默认情况下, 最大连接数不受限制 (NULL))
请求正文最大大小(默认的请求正文最大大小为 30,000,000 字节, 大约 28.6 MB)
请求正文最小数据速率(默认的最小速率为 240 字节 / 秒, 包含 5 秒的宽限期)
请求标头超时(默认值为 30 秒)
每个连接的最大流(默认值为 100)
标题表大小(默认值为 4096)
最大帧大小(默认值为 2^14)
最大请求标头大小(默认值为 8,192)
初始连接窗口大小(默认值为 128 KB)
初始流窗口大小(默认值为 96 KB)
代码如下所示:
- .ConfigureKestrel((context, options) =>
- {
- options.Limits.MaxConcurrentConnections = 100;
- options.Limits.MaxConcurrentUpgradedConnections = 100;
- options.Limits.MaxRequestBodySize = 10 * 1024;
- options.Limits.MinRequestBodyDataRate =
- new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
- options.Limits.MinResponseDataRate =
- new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
- options.Listen(IPAddress.Loopback, 5000);
- options.Listen(IPAddress.Loopback, 5001, listenOptions =>
- {
- listenOptions.UseHttps("testCert.pfx", "testPassword");
- });
- options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
- options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(1);
- options.Limits.Http2.MaxStreamsPerConnection = 100;
- options.Limits.Http2.HeaderTableSize = 4096;
- options.Limits.Http2.MaxFrameSize = 16384;
- options.Limits.Http2.MaxRequestHeaderFieldSize = 8192;
- options.Limits.Http2.InitialConnectionWindowSize = 131072;
- options.Limits.Http2.InitialStreamWindowSize = 98304;
- });
其部分源码如下:
- // Matches the non-configurable default response buffer size for Kestrel in 1.0.0
- private long? _maxResponseBufferSize = 64 * 1024;
- // Matches the default client_max_body_size in nginx.
- // Also large enough that most requests should be under the limit.
- private long? _maxRequestBufferSize = 1024 * 1024;
- // Matches the default large_client_header_buffers in nginx.
- private int _maxRequestLineSize = 8 * 1024;
- // Matches the default large_client_header_buffers in nginx.
- private int _maxRequestHeadersTotalSize = 32 * 1024;
- // Matches the default maxAllowedContentLength in IIS (~28.6 MB)
- // https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
- private long? _maxRequestBodySize = 30000000;
- // Matches the default LimitRequestFields in Apache httpd.
- private int _maxRequestHeaderCount = 100;
- // Matches the default http.sys connectionTimeout.
- private TimeSpan _keepAliveTimeout = TimeSpan.FromMinutes(2);
- private TimeSpan _requestHeadersTimeout = TimeSpan.FromSeconds(30);
- // Unlimited connections are allowed by default.
- private long? _maxConcurrentConnections = null;
- private long? _maxConcurrentUpgradedConnections = null;
参考链接:
来源: https://www.cnblogs.com/edison0621/p/11111199.html