具有跨平臺能力的KestrelServer是最重要的服務器類型。針對KestrelServer的設置均體現在KestrelServerOptions配置選項上,注冊的終結點是它承載的最重要的配置選項。這里所謂的終結點(Endpoint)與“路由”介紹的終結點不是一回事,這里表示的就是服務器在監聽請求時綁定的網絡地址,對應著一個System.Net.Endpoint對象。我們知道ASP.NET Core應用承載API也提供了注冊監聽地址的方法,其本質其實也是為了注冊終結點,那么兩種注冊方式如何取舍呢?[本文節選《ASP.NET Core 6框架揭秘》第18章]
一、UseKestrel擴展方法
二、兩種終結點的取舍
三、終結點配置
四、針對HTTPS的設置
五、限制約束
六、其他設置
一、UseKestrel擴展方法
IWebHostBuilder接口如下三個UseKestrel擴展方法重載會幫助我們完成KestrelServer的注冊并對KestrelServerOptions配置選項作相應設置,我們先來看看如何利用它們來注冊終結點。
public?static?class?WebHostBuilderKestrelExtensions
{public?static?IWebHostBuilder?UseKestrel(this?IWebHostBuilder?hostBuilder);public?static?IWebHostBuilder?UseKestrel(this?IWebHostBuilder?hostBuilder,Action<KestrelServerOptions>?options);public?static?IWebHostBuilder?UseKestrel(this?IWebHostBuilder?hostBuilder,?Action<WebHostBuilderContext,?KestrelServerOptions>?configureOptions);
}
注冊到KestrelServer上的終結點體現為如下這個Endpoint對象。Endpoint是對網絡地址的抽象,它們在大部分下體現為“IP地址+端口”或者“域名+端口”,對應的類型分別為IPEndPoint和DnsEndPoint。UnixDomainSocketEndPoint表示基于Unix Domain Socket/IPC Socket的終結點,它旨在實現同一臺機器上多個進程之間的通信(IPC)。FileHandleEndPoint表示指向某個文件句柄(比如TCP或者Pipe類型的文件句柄)的終結點。
public?abstract?class?EndPoint
{public?virtual?AddressFamily?AddressFamily?{?get;?}public?virtual?EndPoint?Create(SocketAddress?socketAddress);public?virtual?SocketAddress?Serialize();
}public?class?IPEndPoint?:?EndPoint
public?class?DnsEndPoint?:?EndPoint
public?sealed?class?UnixDomainSocketEndPoint?:?EndPoint
public?class?FileHandleEndPoint?:?EndPoint
終結點注冊利用如下這個ListenOptions配置選項來描述。該類型實現的IConnectionBuilder和IMultiplexedConnectionBuilder接口涉及針對連接的構建,我們將在后面討論這個話題。注冊的終結點體現為該配置選項的EndPoint屬性,如果是一個IPEndPoint對象,該對象也會體現在IPEndPoint屬性上。如果終結點類型為UnixDomainSocketEndPoint和FileHandleEndPoint,我們可以利用配置選項的SocketPath和FileHandle得到對應的Socket路徑和文件句柄。
public?class?ListenOptions?:?IConnectionBuilder,?IMultiplexedConnectionBuilder
{public?EndPoint?EndPoint?{?get;?}public?IPEndPoint?IPEndPoint?{?get;?}public?string?SocketPath?{?get;?}public?ulong?FileHandle?{?get;?}public?HttpProtocols?Protocols?{?get;?set;?}public?bool?DisableAltSvcHeader?{?get;?set;?}public?IServiceProvider?ApplicationServices?{?get;?}public?KestrelServerOptions?KestrelServerOptions?{?get;?}...
}
同一個終結點可以同時支持HTTP 1.x、HTTP 2 和HTTP 3三種協議,具體設置體現在Protocols屬性上,該屬性返回如下這個HttpProtocols枚舉。由于枚舉項Http3和Http1AndHttp2AndHttp3上面標注了RequiresPreviewFeaturesAttribute特性,如果需要采用HTTP 3協議,項目文件中必須添加“<EnablePreviewFeatures>true</EnablePreviewFeatures>”屬性。如果HTTP3終結點同時支持HTTP 1.X和HTTP 2,針對HTTP 1.X和HTTP 2的請求的響應一般會添加一個alt-svc (Alternative Service)報頭指示可以升級到HTTP 3,我們可以設置DisableAltSvcHeader屬性關閉此特性。該屬性默認值為Http1AndHttp2。
[Flags]
public?enum?HttpProtocols
{None?=?0,Http1?=?1,Http2?=?2,Http1AndHttp2?=?3,[RequiresPreviewFeatures]Http3?=?4,[RequiresPreviewFeatures]Http1AndHttp2AndHttp3?=?7
}
KestrelServerOptions的ListenOptions屬性返回的ListenOptions列表代表所有注冊的終結點,它由CodeBackedListenOptions和ConfigurationBackedListenOptions屬性合并而成,這兩個屬性分別表示通過代碼和配置注冊的終結點。基于“代碼”的終結點注冊由如下所示的一系列Listen和以“Listen”為前綴的方法來完成。除了這些注冊單個終結點的方法, ConfigureEndpointDefaults方法為注冊的所有終結點提供基礎設置。
public?class?KestrelServerOptions
{internal?List<ListenOptions>??CodeBackedListenOptions?{?get;?}internal?List<ListenOptions>??ConfigurationBackedListenOptions?{?get;?}internal?IEnumerable<ListenOptions>?????ListenOptions?{?get;?}public?void?Listen(EndPoint?endPoint);public?void?Listen(IPEndPoint?endPoint);public?void?Listen(EndPoint?endPoint,?Action<ListenOptions>?configure);public?void?Listen(IPAddress?address,?int?port);public?void?Listen(IPEndPoint?endPoint,?Action<ListenOptions>?configure);public?void?Listen(IPAddress?address,?int?port,?Action<ListenOptions>?configure);public?void?ListenAnyIP(int?port);public?void?ListenAnyIP(int?port,?Action<ListenOptions>?configure);public?void?ListenHandle(ulong?handle);public?void?ListenHandle(ulong?handle,?Action<ListenOptions>?configure);public?void?ListenLocalhost(int?port);public?void?ListenLocalhost(int?port,?Action<ListenOptions>?configure);public?void?ListenUnixSocket(string?socketPath);public?void?ListenUnixSocket(string?socketPath,?Action<ListenOptions>?configure);public?void?ConfigureEndpointDefaults(Action<ListenOptions>?configureOptions)...
}
二、兩種終結點的取舍
我們知道監聽地址不僅可以添加到WebApplication對象的Urls屬性中,WebApplication類型用來啟動應用的RunAsync和Run方法也提供了可缺省的參數url來指定監聽地址。從如下的代碼片段可以看出,這三種方式提供的監聽地址都被添加到了IServerAddressesFeature特性的Addresses屬性中。
public?sealed?class?WebApplication?:?IHost
{private?readonly?IHost?_host;public?ICollection<string>?Urls?=>?_host.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>()?.Addresses????throw?new?InvalidOperationException("IServerAddressesFeature?could?not?be?found.");public?Task?RunAsync(string??url?=?null){Listen(url);return?((IHost)this).RunAsync();}public?void?Run(string??url?=?null){Listen(url);((IHost)this).Run();}private?void?Listen(string??url){if?(url?!=?null){var?addresses?=?ServerFeatures.Get<IServerAddressesFeature>()?.Addresses????throw?new?InvalidOperationException("No?valid?IServerAddressesFeature?is?found");addresses.Clear();addresses.Add(url);}}
}
如果KestrelServerOptions配置選項不能提供注冊的終結點,那么KestrelServer就會使用IServerAddressesFeature特性提供的地址來創建對應的終結點,否則就會根據它的PreferHostingUrls屬性來進行取舍。如果IServerAddressesFeature特性的PreferHostingUrls屬性返回True,它提供的地址會被選擇,否則就使用直接注冊到KestrelServerOptions配置選項的終結點。針對監聽地址的注冊和PreferHostingUrls的設置可以利用IWebHostBuilder接口如下兩個擴展方法來完成。從給出的代碼片段可以看出這兩個方法會將提供的設置存儲配置上,配置項名稱分別為“urls”和“preferHostingUrls”,對應著WebHostDefaults定義的兩個靜態只讀字段ServerUrlsKey和PreferHostingUrlsKey。既然這兩個設置來源于配置,我們自然可以利用命令行參數、環境變量或者直接修改對應配置項的方式來指定它們。
public?static?class?HostingAbstractionsWebHostBuilderExtensions
{public?static?IWebHostBuilder?UseUrls(this?IWebHostBuilder?hostBuilder,?params?string[]?urls)=>?hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey,?string.Join(';',?urls));public?static?IWebHostBuilder?PreferHostingUrls(this?IWebHostBuilder?hostBuilder,?bool?preferHostingUrls)=>?hostBuilder.UseSetting(WebHostDefaults.PreferHostingUrlsKey,?preferHostingUrls???"true"?:?"false");
}
如果服務器的特性集合提供的IServerAddressesFeature特性包含監聽地址,以配置方式設置的監聽地址和針對PreferHostingUrls的設置將會被忽略,這一個特性體現在GenericWebHostService的StartAsync方法中。如下面的代碼片段所示,該方法會從服務器中提取IServerAddressesFeature特性,只有該特性不能提供監聽地址的情況下,利用配置注冊的監聽地址和針對PreferHostingUrls的設置才會應用到該特性中。
internal?sealed?class?GenericWebHostService?:?IHostedService
{public?async?Task?StartAsync(CancellationToken?cancellationToken){...var?serverAddressesFeature?=?Server.Features.Get<IServerAddressesFeature>();var?addresses?=?serverAddressesFeature?.Addresses;if?(addresses?!=?null?&&?!addresses.IsReadOnly?&&?addresses.Count?==?0){var?text?=?Configuration[WebHostDefaults.ServerUrlsKey];if?(!string.IsNullOrEmpty(text)){serverAddressesFeature.PreferHostingUrls?=?WebHostUtilities.ParseBool(Configuration,?WebHostDefaults.PreferHostingUrlsKey);string[]?array?=?text.Split(';',?StringSplitOptions.RemoveEmptyEntries);foreach?(string?item?in?array){addresses.Add(item);}}}}
}
下面的演示程序通過調用IWebHostBuilder接口的UseKestrel擴展方法注冊了一個采用8000端口的本地終結點,通過調用UseUrls擴展方法注冊了一個采用9000端口的監聽地址。
var?builder?=?WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel(kestrel?=>?kestrel.ListenLocalhost(8000)).UseUrls("http://localhost:9000");
var?app?=?builder.Build();
app.Run();
我們以命令行的方式兩次啟動了該程序。默認情況下應用會選擇調用UseKestrel擴展方法注冊的終結點。如果指定了命令行參數“preferHostingUrls=1”,那么最終使用的都是將是調用UseUrls擴展方法注冊的監聽地址。由于兩種情況都涉及到放棄某種設置,所以輸出了相應的日志。
圖1 兩種終結點的選擇
三、終結點配置
KestrelServerOptions承載的很多設置都可以利用配置來提供。由于該配置選項類型的定義與配置的結構存在差異, KestrelServerOptions配置選項無法直接使用對應的IConfiguration對象進行綁定,所以KestrelServerOptions類型定義如下三個Configure方法。后面兩個方法提供了承載配置內容的IConfiguration對象,最后一個重載還提供了reloadOnChange參數來決定是否自動加載更新后的配置。第一個重載提供的其實是一個空的IConfiguration對象。
public?class?KestrelServerOptions
{public?KestrelConfigurationLoader?Configure();public?KestrelConfigurationLoader?Configure(IConfiguration?config);public?KestrelConfigurationLoader?Configure(IConfiguration?config,?bool?reloadOnChange)
}
三個Configure方法都返回KestrelConfigurationLoader對象,后者是對當前KestrelServerOptions配置選項和指定IConfiguration對象的封裝。KestrelConfigurationLoader的Load方法會讀取配置的內容并將其應用到KestrelServerOptions配置選項上,該類型還提供了一系列注冊各類終結點的方法。
public?class?KestrelConfigurationLoader
{public?KestrelServerOptions?Options?{?get;?}public?IConfiguration?Configuration?{?get;?}public?KestrelConfigurationLoader?Endpoint(string?name,?Action<EndpointConfiguration>?configureOptions);public?KestrelConfigurationLoader?Endpoint(IPAddress?address,?int?port);public?KestrelConfigurationLoader?Endpoint(IPAddress?address,?int?port,?Action<ListenOptions>?configure);public?KestrelConfigurationLoader?Endpoint(IPEndPoint?endPoint);public?KestrelConfigurationLoader?Endpoint(IPEndPoint?endPoint,?Action<ListenOptions>?configure);public?KestrelConfigurationLoader?LocalhostEndpoint(int?port);public?KestrelConfigurationLoader?LocalhostEndpoint(int?port,?Action<ListenOptions>?configure);public?KestrelConfigurationLoader?AnyIPEndpoint(int?port);public?KestrelConfigurationLoader?AnyIPEndpoint(int?port,?Action<ListenOptions>?configure);public?KestrelConfigurationLoader?UnixSocketEndpoint(string?socketPath);public?KestrelConfigurationLoader?UnixSocketEndpoint(string?socketPath,?Action<ListenOptions>?configure);public?KestrelConfigurationLoader?HandleEndpoint(ulong?handle);public?KestrelConfigurationLoader?HandleEndpoint(ulong?handle,?Action<ListenOptions>?configure);public?void?Load();
}
ASP.NET Core應用在啟動時會調用IHostBuilder接口如下這個ConfigureWebHostDefaults擴展方法進行初始化設置,該方法會從當前配置中提取出“Kestrel”配置節,并將其作為參數調用Configure方法將配置內容應用到KestrelServerOptions配置選項上。由于reloadOnChange參數被設置成了True,所以更新后的配置會自動被重新加載。
public?static?class?GenericHostBuilderExtensions
{public?static?IHostBuilder?ConfigureWebHostDefaults(this?IHostBuilder?builder,?Action<IWebHostBuilder>?configure)?=>?builder.ConfigureWebHost(webHostBuilder?=>?{WebHost.ConfigureWebDefaults(webHostBuilder);configure(webHostBuilder);});
}public?static?class?WebHost
{internal?static?void?ConfigureWebDefaults(IWebHostBuilder?builder){...builder.UseKestrel((builderContext,?options)?=>?options.Configure(builderContext.Configuration.GetSection("Kestrel"),?reloadOnChange:?true)...}
}
如下的代碼片段展現了針對終結點的配置。我們在“Kestrel:Endpoints”配置了兩個分別命名為“endpoint1”和“endpoint2”終結點,它們采用的監聽地址分別為“http://localhost:9000”和“https://localhost:9001”。KestrelServerOptions絕大部分配置選項都可以定義在配置文件中,具體的配置定義方法可以參閱官方文檔。
{"Kestrel":?{"Endpoints":?{"endpoint1":?{"Url":?"http://localhost:9000"},"endpoint2":?{"Url":?"https://localhost:9001"}}}
}
四、針對HTTPS的設置
較之普通的終結點,HTTPS(SSL/TLS)終結點需要提供額外的設置,這些設置大都體現在如下這個HttpsConnectionAdapterOptions配置選項上。KestrelServerOptions的ConfigureHttpsDefaults方法為所有HTTPS終結點提供了默認的設置。
public?class?HttpsConnectionAdapterOptions
{public?X509Certificate2??ServerCertificate?{?get;?set;?}public?Func<ConnectionContext?,?string?,?X509Certificate2?>??ServerCertificateSelector?{?get;?set;?}public?TimeSpan?HandshakeTimeout?{?get;?set;?}public?SslProtocols?SslProtocols?{?get;?set;?}public?Action<ConnectionContext,?SslServerAuthenticationOptions>??OnAuthenticate?{?get;?set;?}public?ClientCertificateMode?ClientCertificateMode?{?get;?set;?}public?Func<X509Certificate2,?X509Chain?,?SslPolicyErrors,?bool>??ClientCertificateValidation?{?get;?set;?}public?bool?CheckCertificateRevocation?{?get;?set;?}public?void?AllowAnyClientCertificate()?{?get;?set;?}
}public?static?class?KestrelServerOptions
{public?void?ConfigureHttpsDefaults(Action<HttpsConnectionAdapterOptions>?configureOptions);...
}
表示服務端證書的X509Certificate2對象可以直接設置到ServerCertificate屬性上,我們也可以在ServerCertificateSelector屬性上設置一個根據當前連結動態選擇證書的委托。SslProtocols屬性用來設置采用的協議(SSL或者TLS),對應的類型為如下這個SslProtocols枚舉。HandshakeTimeout屬性用來設置TLS/SSL“握手”的超時時間,默認為10秒。
[Flags]
public?enum?SslProtocols
{None?=?0x0,[Obsolete("SslProtocols.Ssl2?has?been?deprecated?and?is?not?supported.")]Ssl2?=?0xC,[Obsolete("SslProtocols.Ssl3?has?been?deprecated?and?is?not?supported.")]Ssl3?=?0x30,Tls?=?0xC0,[Obsolete("SslProtocols.Default?has?been?deprecated?and?is?not?supported.")]Default?=?0xF0,Tls11?=?0x300,Tls12?=?0xC00,Tls13?=?0x3000
}
HTTPS主要解決的是服務端的認證和傳輸安全問題,所以服務端的認證信息需要在前期“協商”階段利用建立的安全通道傳遞給客戶端,具體的認證信息是如下這個SslServerAuthenticationOptions配置選項格式化后的結果。HttpsConnectionAdapterOptions的OnAuthenticate屬性提供的委托可以幫助我們對這個配置選項進行設置,所以絕大部分HTTPS相關的設置都可以利用該屬性來完成。
public?class?SslServerAuthenticationOptions
{public?bool?AllowRenegotiation?{?get;?set;?}public?bool?ClientCertificateRequired?{?get;?set;?}public?List<SslApplicationProtocol>??ApplicationProtocols?{?get;?set;?}public?RemoteCertificateValidationCallback??RemoteCertificateValidationCallback?{?get;?set;?}public?ServerCertificateSelectionCallback??ServerCertificateSelectionCallback?{?get;?set;?}public?X509Certificate??ServerCertificate?{?get;?set;?}public?SslStreamCertificateContext??ServerCertificateContext?{?get;?set;?}public?SslProtocols?EnabledSslProtocols?{?get;?set;?}public?X509RevocationMode?CertificateRevocationCheckMode?{?get;?set;?}public?EncryptionPolicy?EncryptionPolicy?{?get;?set;?}public?CipherSuitesPolicy??CipherSuitesPolicy?{?get;?set;?}
}
HTTPS不僅僅能夠幫助客戶端來驗證服務端的身份,還能幫助服務端來對客戶端身份進行驗證。服務端驗證利用服務端證書來完成,與之類似,服務端要識別客戶端的身份,同樣需要客戶端提供證書。我們可以利用HttpsConnectionAdapterOptions的ClientCertificateMode屬性來決定是否要求客戶端提供證書,該屬性類型為如下這個ClientCertificateMode枚舉。針對客戶端認證的驗證可以利用ClientCertificateValidation屬性設置的委托來完成。
public?enum?ClientCertificateMode
{NoCertificate,AllowCertificate,RequireCertificate,DelayCertificate
}
由權威機構(Certificate Authority)頒發的證書可能會由于某種原因被撤銷,我們有兩種途徑來確定某張證書是否處于被撤銷的狀態:證書頒發機構可以采用標準的OCSP(Online Certificate Status Protocol)協議提供用于確定證書狀態的API,也可以直接提供一份撤銷的證書清單(CRL:Certificate Revocation List)。HttpsConnectionAdapterOptions的CheckCertificateRevocation屬性用來決定是否需要對證書的撤銷狀態進行驗證。如果不需要對客戶端證書作任何驗證,我們可以調用HttpsConnectionAdapterOptions的AllowAnyClientCertificate方法。
當我們將某個終結點注冊到KestrelServer上并生成對應ListenOptions配置選項后,我們可以調用后者的UseHttps擴展方法(注冊終結點的很多方法都提供一個Action<ListenOptions>參數)完成針對HTTPS的設置,我們有如下這一系列UseHttps重載可供選擇。對于證書的設置,我們可以直接指定一個X509Certificate2對象,也可以指定證書文件的路徑(一般還需要提供讀取證書的密碼),還可以指定證書的存儲(Certificate Store)。我們可以利用部分方法重載提供的委托對HttpsConnectionAdapterOptions配置選項進行設置。部分方法重載還提供了一個ServerOptionsSelectionCallback委托直接返回SslServerAuthenticationOptions配置選項。
public?static?class?ListenOptionsHttpsExtensions
{public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?string?fileName);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?string?fileName,?string??password);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?string?fileName,?string??password,?Action<HttpsConnectionAdapterOptions>?configureOptions);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?StoreName?storeName,?string?subject);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?StoreName?storeName,?string?subject,?bool?allowInvalid);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?StoreName?storeName,?string?subject,?bool?allowInvalid,?StoreLocation?location);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?StoreName?storeName,?string?subject,?bool?allowInvalid,?StoreLocation?location,?Action<HttpsConnectionAdapterOptions>?configureOptions);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?X509Certificate2?serverCertificate);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?X509Certificate2?serverCertificate,?Action<HttpsConnectionAdapterOptions>?configureOptions);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?Action<HttpsConnectionAdapterOptions>?configureOptions);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?HttpsConnectionAdapterOptions?httpsOptions);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?ServerOptionsSelectionCallback?serverOptionsSelectionCallback,?object?state);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?ServerOptionsSelectionCallback?serverOptionsSelectionCallback,?object?state,?TimeSpan?handshakeTimeout);public?static?ListenOptions?UseHttps(this?ListenOptions?listenOptions,?TlsHandshakeCallbackOptions?callbackOptions);
}public?delegate?ValueTask<SslServerAuthenticationOptions>?ServerOptionsSelectionCallback(SslStream?stream,?SslClientHelloInfo?clientHelloInfo,?object??state,?CancellationToken?cancellationToken);
除了調用上述這些方法來為注冊的終結點提供HTTPS相關的設置外,這些設置也可以按照如下的方式放在終結點的配置中。
{"Kestrel":?{"Endpoints":?{"MyHttpsEndpoint":?{"Url":?"https://localhost:5001","ClientCertificateMode":?"AllowCertificate","Certificate":?{"Path":?"c:\\certificates\\foobar.pfx>","Password":?"password"}}}}
}
五、限制約束
為了確保KestrelServer穩定可靠地運行,需要根據需要為它設置相應的限制和約束,這些設置體現在KestrelServerOptions配置選項Limits屬性返回的KestrelServerLimits對象上。
public?class?KestrelServerOptions
{public?KestrelServerLimits?Limits?{?get;?}?=?new?KestrelServerLimits();
}public?class?KestrelServerLimits
{public?long??MaxConcurrentConnections?{?get;?set;?}public?long??MaxConcurrentUpgradedConnections?{?get;?set;?}public?TimeSpan?KeepAliveTimeout?{?get;?set;?}public?int?MaxRequestHeaderCount?{?get;?set;?}public?long??MaxRequestBufferSize?{?get;?set;?}public?int?MaxRequestHeadersTotalSize?{?get;?set;?}public?int?MaxRequestLineSize?{?get;?set;?}public?long??MaxRequestBodySize?{?get;?set;?}public?TimeSpan?RequestHeadersTimeout?{?get;?set;?}public?MinDataRate?MinRequestBodyDataRate?{?get;?set;?}public?long??MaxResponseBufferSize?{?get;?set;?}public?MinDataRate?MinResponseDataRate?{?get;?set;?}public?Http2Limits?Http2?{?get;?}public?Http3Limits?Http3?{?get;?}
}
KestrelServerLimits利用其豐富的屬性對連接、請求和響應進行了相應的限制。KestrelServer提供了針對HTTP 2和HTTP3的支持,針對性的限制設置體現在KestrelServerLimits類型的Http2和Http3屬性上。下表對定義在KestrelServerLimits類型中的這些屬性所體現的限制約束進行了簡單說明。
屬性 | 含 義 |
MaxConcurrentConnections | 最大并發連接。如果設置為Null(默認值),意味著不作限制。 |
MaxConcurrentUpgradedConnections | 可升級連接(比如從HTTP升級到WebSocket)的最大并發數。如果設置為Null(默認值),意味著不作限制。 |
KeepAliveTimeout | 連接保持活動狀態的超時時間,默認值為130秒。 |
MaxRequestHeaderCount | 請求攜帶的最大報頭數量,默認值為100。 |
MaxRequestBufferSize | 請求緩沖區最大容量,默認值為1,048,576字節(1M)。 |
MaxRequestHeadersTotalSize | 請求攜帶報頭總字節數,默認值為 32,768字節(32K)。 |
MaxRequestLineSize | 對于HTTP 1.X來說就是請求的首行(Request Line)最大字節數。對于HTTP 2/3來說就是 :method, :scheme, :authority, and :path這些報頭的總字節數。默認值為8,192 字節(8K)。 |
MaxRequestBodySize | 請求主體最大字節數,默認值為30,000,000 字節(約28.6M)。如果設置為Null,意味著不作限制。 |
RequestHeadersTimeout | 接收請求報頭的超時時間,默認為30秒。 |
MinRequestBodyDataRate | 請求主體內容最低傳輸率。 |
MaxResponseBufferSize | 響應緩沖區最大容量,默認值為65,536(1M)。 |
MinResponseDataRate | 響應最低傳輸率。 |
KestrelServerLimits的MinRequestBodyDataRate和MinResponseDataRate屬性返回的最低傳輸率體現為如下這個MinDataRate對象。如果沒有達到設定的傳輸率,當前連接就會被重置。MinDataRate對象除了提供表示傳輸率的BytesPerSecond屬性外,還提供了一個表示“寬限時間”的GracePeriod屬性。并非傳輸率下降到設定的閾值的那一刻就重置連接,只要在指定的時段內傳輸率上升到閾值以上也沒有問題。MinRequestBodyDataRate和MinResponseDataRate屬性的默認值均為“240 bytes/second(5 seconds)”。
public?class?MinDataRate
{public?double?BytesPerSecond?{?get;?}public?TimeSpan?GracePeriod?{?get;?}public?MinDataRate(double?bytesPerSecond,?TimeSpan?gracePeriod);
}
HTTP 1.X建立在TCP之上,客戶端和服務端之間的交互依賴預先創建的TCP連接。雖然HTTP 1.1引入的流水線技術允許客戶端可以隨時向服務端發送請求,而無需等待接收到上一個請求的響應,但是響應依然只能按照請求的接收順序返回的。真正意義上的“并發”請求只能利用多個連接來完成,但是針對同一個域名支持的TCP連接的數量又是有限的。這個問題在HTTP 2得到了一定程度的解決。
與采用文本編碼的HTTP 1.X相比, HTTP 2采用更加高效的二進制編碼。幀(Frame)成為了基本通信單元,單個請求和響應可以分解成多個幀進行發送。客戶端和服務端之間額消息交換在一個支持雙向通信的信道(Channel)中完成,該信道被稱為“流(Stream)”。每一個流具有一個唯一標識,同一個TCP連接可以承載成百上千的流。每個幀攜帶著所屬流的標識,所以它可以隨時被“亂序”發送,接收端可以利用流的標識進行重組,所以HTTP 2在同一個TCP連接上實現了“多路復用”。
使用同一個連接發送的請求和響應都存在很多重復的報頭,為了減少報頭內容占據的帶寬,HTTP 2會采用一種名為HPACK的壓縮算法對報頭文本進行編碼。HPACK會在發送和接收端維護一個索引表來存儲編碼的文本,報頭內容在發送前會被替換成在該表的索引,接收端這利用此索引在本地壓縮表中找到原始的內容。
public?class?Http2Limits
{public?int?????MaxStreamsPerConnection?{?get;?set;?}public?int?????HeaderTableSize?{?get;?set;?}public?int?????MaxFrameSize?{?get;?set;?}public?int?????MaxRequestHeaderFieldSize?{?get;?set;?}public?int?????InitialConnectionWindowSize?{?get;?set;?}public?int?????InitialStreamWindowSize?{?get;?set;?}public?TimeSpan?????KeepAlivePingDelay?{?get;?set;?}public?TimeSpan?????KeepAlivePingTimeout?{?get;?set;?}
}
于HTTP 2相關限制和約束的設置體現在KestrelServerLimits的Http2屬性上,該屬性返回如上所示的Http2Limits對象。下表對定義在Http2Limits類型中的這些屬性所體現的限制約束進行了簡單說明。
屬性 | 含 義 |
MaxStreamsPerConnection | 連接能夠承載的流數量,默認值為100。 |
HeaderTableSize | HPACK報頭壓縮表的容量,默認值為4096。 |
MaxFrameSize | 幀的最大字節數,有效值在[214~224?– 1]區間范圍內,默認值為214(16384)。 |
MaxRequestHeaderFieldSize | 最大請求報頭(含報頭名稱)的最大字節數,默認值為214(16384)。 |
InitialConnectionWindowSize | 連接的初始化請求主體緩存區的大小,有效值在[65535~231]區間范圍內,默認為131072。 |
InitialStreamWindowSize | 流的初始化請求主體緩存區的大小,有效值在[65535~231]區間范圍內,默認為98304。 |
KeepAlivePingDelay | 如果服務端在該屬性設定的時間跨度內沒有接收到來自客戶端的有效幀,它會主動發送Ping請求確定客戶端的是否保持活動狀態,默認值為1秒。 |
KeepAlivePingTimeout | 發送Ping請求的超時時間,如果客戶端在該時限內一直處于為活動狀態,當前連接將被關閉,默認值為20秒。 |
由于HTTP 2的多路復用是在同一個TCP連接上實現的,這樣的實現并不“純粹”,因為它不可能解決由于TCP的“擁塞控制”機制導致的“隊頭阻塞(Header-Of-Line Blocking)”問題。如果希望在得到并發支持的前提下還能在低延時上有更好的作為,就不得不拋棄TCP。目前被正式確定為HTTP 3的QUIC(Quick UDP Internet Connection)就將TCP替換成了UDP。如果KestrelServer支持HTTP 3,我們可以利用KestrelServerLimits的Http3屬性返回的Http3Limits對象都限制約束進行針對性設置。Http3Limits只包含如下這個表示最大請求報頭字節數的MaxRequestHeaderFieldSize屬性,它的默認值為16384。
public?class?Http3Limits
{public?int?MaxRequestHeaderFieldSize?{?get;?set;}
}
六、其他設置
除了注冊的終結點和基于通信的限制約束,KestrelServerOptions配置選項還利用如下的屬性承載著其他的設置。
public?class?KestrelServerOptions
{public?bool?AddServerHeader?{?get;?set;?}public?bool?AllowResponseHeaderCompression?{?get;?set;?}public?bool?AllowSynchronousIO?{?get;?set;?}public?bool?AllowAlternateSchemes?{?get;?set;?}public?bool?DisableStringReuse?{?get;?set;?}public?Func<string,?Encoding>?RequestHeaderEncodingSelector?{?get;?set;?}public?Func<string,?Encoding>?ResponseHeaderEncodingSelector?{?get;?set;?}
}
下表對定義在KestrelServerOptions類型中的上述這些屬性進行了簡單的說明。
屬性 | 含 義 |
AddServerHeader | 是否會在回復的響應中自動添加“Server: Kestrel”報頭,默認值為True。 |
AllowResponseHeaderCompression | 是否允許對響應報頭進行HPACK壓縮,默認值為True。 |
AllowSynchronousIO | 是否允許對請求和響應進行同步IO操作,默認值為False,意味這個默認情況下以同步方式讀取請求和寫入響應都會拋出異常。 |
AllowAlternateSchemes | 是否允許為“:scheme”字段(針對HTTP 2和HTTP 3)提供一個與當前傳輸不匹配的值(“http”或者“https”),默認值為False。如果將這個屬性設置為True,意味著HttpRequest.Scheme屬性可能與采用的傳輸類型不匹配。 |
DisableStringReuse | 創建的字符串是否可以在多個請求中復用。 |
RequestHeaderEncodingSelector | 用于設置某個請求報頭采用的編碼方式,默認為Utf8Encoding。 |
ResponseHeaderEncodingSelector | 用于設置某個響應報頭采用的編碼方式,默認為ASCIIEncoding。 |