ASP.NET Core 的 Web 服務器默認採用Kestrel,這是一個基於libuv(一個跨平臺的基於Node.js異步I/O庫)的跨平臺、輕量級的Web服務器。html
在開始以前,先回顧一下.NET Core 3.0默認的main()方法模板中,咱們會調用Host.CreateDefaultBuilder方法,該方法的主要功能是配置應用主機及設置主機的屬性,設置Kestrel 服務器配置爲 Web 服務器,另外還包括日誌功能、應用配置加載等等,此處不作展開。nginx
做爲一個輕量級的Web Server,它並無IIS、Apache那些大而全的功能,但它依然能夠單獨運行,也能夠搭配IIS、Apache等反向代理服務器結合使用。web
本文將從源碼角度討論ASP.NET Core應用在Kestrel的相關知識點。編程
瞭解這個問題,首先須要強調的是.NET Core應用的目標就是跨平臺,既然要跨平臺那麼就須要適用各個平臺上的Web服務器,各個服務器的啓動、配置等等都是不盡相同的,若是每一個服務器提供一套實現出來,若是將來出現了一個新的Web Server,而後又要增長新的實現,這會致使.NET Core應用的適用性滯後,也會很消耗人力,沒法很好的達到跨平臺的目標。api
咱們能夠把Kestrel視做一箇中間件,一個適配的功能,它抽象了各個服務器的特性,使得各個應用只須要調用一樣的接口,便可最大限度的在各個平臺上運行。服務器
.NET Core 3.0下,Kestrel的集成已經至關成熟了,也提供了相應的自定義配置,以使得Kestrel的使用更加具備靈活性和可配性。它能夠獨立運行,也能夠與反向代理服務器結合使用。app
Kestrel自己是不支持多個應用共享同一個端口的,可是咱們能夠經過反向代理服務器來實現統一對外的相同的端口的共享。less
如下是其單獨運行示意圖:異步
如下是其結合反向代理使用示意圖:函數
該類庫是Kestrel的核心類庫,裏面包含了該功能的多個邏輯實現,如下簡稱改類庫爲Kestrel.Core。
如前文所說,Kestrel起到了抽象服務器的功能,那麼在適配其餘服務器的過程當中,必然涉及到的是,輸入、輸出、數據交互方式以及Trace功能。在Kestrel.Core中,該功能主要由AdaptedPipeline類來實現,該類繼承自IDuplexPipe,並經過構造函數獲取到了Pipe對象。IDuplexPipe和Pipe均位於System.IO.Pipelines命名空間下,詳細信息能夠點擊查看。
AdaptedPipeline有兩個公共方法:
RunAsync():用於讀取(讀取後會有Flush操做)和寫入數據,並分別裝載到Task中
CompleteAsync():完成讀取和寫入操做,並取消基礎流的讀取
另外還包括四個公共屬性,以下所示:
1: public RawStream TransportStream { get; }
2:
3: public Pipe Input { get; }
4:
5: public Pipe Output { get; }
6:
7: 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不一樣的是,裏面增長操做過程的日誌記錄,主要用於記錄在鏈接適配過程當中的信息,不過須要啓用日誌才能把日誌信息記錄下來,如下是其對外的使用方式:
1: public static class ListenOptionsConnectionLoggingExtensions
2: {
3: /// <summary>
4: /// Emits verbose logs for bytes read from and written to the connection.
5: /// </summary>
6: /// <returns>
7: /// The <see cref="ListenOptions"/>.
8: /// </returns>
9: public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions)
10: {
11: return listenOptions.UseConnectionLogging(loggerName: null);
12: }
13:
14: /// <summary>
15: /// Emits verbose logs for bytes read from and written to the connection.
16: /// </summary>
17: /// <returns>
18: /// The <see cref="ListenOptions"/>.
19: /// </returns>
20: public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions, string loggerName)
21: {
22: var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
23: var logger = loggerName == null ? loggerFactory.CreateLogger<LoggingConnectionAdapter>() : loggerFactory.CreateLogger(loggerName);
24: listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(logger));
25: return listenOptions;
26: }
27: }
該模塊下的 Kestrel特性,比較重要的有鏈接超時設置(包括設置超時時間、重置超時時間以及取消超時限制。這個特性使得咱們的鏈接變得更加可控,好比,在某些特殊場景下,特殊條件下,咱們須要取消超時限制或者動態重置超時時間),TLS應用程序協議功能,基於Http2.0的StreamId記錄功能,用於中止鏈接計數的功能。
如下是鏈接超時接口的源代碼:
1: /// <summary>
2: /// Feature for efficiently handling connection timeouts.
3: /// </summary>
4: public interface IConnectionTimeoutFeature
5: {
6: /// <summary>
7: /// Close the connection after the specified positive finite <see cref="TimeSpan"/>
8: /// unless the timeout is canceled or reset. This will fail if there is an ongoing timeout.
9: /// </summary>
10: void SetTimeout(TimeSpan timeSpan);
11:
12: /// <summary>
13: /// Close the connection after the specified positive finite <see cref="TimeSpan"/>
14: /// unless the timeout is canceled or reset. This will cancel any ongoing timeouts.
15: /// </summary>
16: void ResetTimeout(TimeSpan timeSpan);
17:
18: /// <summary>
19: /// Prevent the connection from closing after a timeout specified by <see cref="SetTimeout(TimeSpan)"/>
20: /// or <see cref="ResetTimeout(TimeSpan)"/>.
21: /// </summary>
22: void CancelTimeout();
23: }
Kestrel的選項控制包括監聽、Kestrel服務器、HTTPS鏈接適配。
一、監聽選項功能在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,以用於特定場景的監聽使用。
二、Kestrel服務器選項是在KestrelServerOptions中實現的,該類用於提供Kestrel特定功能的編程級別配置,該類內部會維護ListenOptions的列表對象,該類將ListenOptions的功能進一步展開,並加入了HTTPS、證書的默認配置與應用,這個類比較大,本文就不貼出源碼了,有興趣的同窗能夠本身去翻閱。
三、HTTPS鏈接適配選項在HttpsConnectionAdapterOptions實現,這個類用於設置Kestrel如何處理HTTPS鏈接,這裏引入和證書功能、SSL協議、HTTP協議、超時功能,同時這裏還能夠自定義HTTPS鏈接的時候的證書處理模式(AllowCertificate、RequireCertificate等),如下是HttpsConnectionAdapterOptions的構造函數:
1: public HttpsConnectionAdapterOptions()
2: {
3: ClientCertificateMode = ClientCertificateMode.NoCertificate;
4: SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
5: HandshakeTimeout = TimeSpan.FromSeconds(10);
6: }
能夠看到,在默認狀況下,是無證書模式,其SSL協議包括Tls12 和Tls11以及指定容許進行TLS/SSL握手的最大時間是十秒鐘。
四、Kestrel的限制功能在KestrelServerLimits實現,主要包括:
代碼以下所示:
1: .ConfigureKestrel((context, options) =>
2: {
3: options.Limits.MaxConcurrentConnections = 100;
4: options.Limits.MaxConcurrentUpgradedConnections = 100;
5: options.Limits.MaxRequestBodySize = 10 * 1024;
6: options.Limits.MinRequestBodyDataRate =
7: new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
8: options.Limits.MinResponseDataRate =
9: new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
10: options.Listen(IPAddress.Loopback, 5000);
11: options.Listen(IPAddress.Loopback, 5001, listenOptions =>
12: {
13: listenOptions.UseHttps("testCert.pfx", "testPassword");
14: });
15: options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
16: options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(1);
17: options.Limits.Http2.MaxStreamsPerConnection = 100;
18: options.Limits.Http2.HeaderTableSize = 4096;
19: options.Limits.Http2.MaxFrameSize = 16384;
20: options.Limits.Http2.MaxRequestHeaderFieldSize = 8192;
21: options.Limits.Http2.InitialConnectionWindowSize = 131072;
22: options.Limits.Http2.InitialStreamWindowSize = 98304;
23: });
1: // Matches the non-configurable default response buffer size for Kestrel in 1.0.0
2: private long? _maxResponseBufferSize = 64 * 1024;
3:
4: // Matches the default client_max_body_size in nginx.
5: // Also large enough that most requests should be under the limit.
6: private long? _maxRequestBufferSize = 1024 * 1024;
7:
8: // Matches the default large_client_header_buffers in nginx.
9: private int _maxRequestLineSize = 8 * 1024;
10:
11: // Matches the default large_client_header_buffers in nginx.
12: private int _maxRequestHeadersTotalSize = 32 * 1024;
13:
14: // Matches the default maxAllowedContentLength in IIS (~28.6 MB)
15: // https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
16: private long? _maxRequestBodySize = 30000000;
17:
18: // Matches the default LimitRequestFields in Apache httpd.
19: private int _maxRequestHeaderCount = 100;
20:
21: // Matches the default http.sys connectionTimeout.
22: private TimeSpan _keepAliveTimeout = TimeSpan.FromMinutes(2);
23:
24: private TimeSpan _requestHeadersTimeout = TimeSpan.FromSeconds(30);
25:
26: // Unlimited connections are allowed by default.
27: private long? _maxConcurrentConnections = null;
28: private long? _maxConcurrentUpgradedConnections = null;