ASP.NET Core設(shè)計(jì)初衷是開(kāi)源跨平臺(tái)、高性能Web服務(wù)器,其中跨平臺(tái)特性較早期ASP.NET是一個(gè)顯著的飛躍,.NET現(xiàn)可以理直氣壯與JAVA同臺(tái)競(jìng)技,而ASP.NET Core的高性能特性更是成為致勝法寶。
ASP.NET Core 2.1+為IIS托管新增In-Process模型并作為默認(rèn)選項(xiàng)(使用IISHttpServer替代了Kestrel,dotnet程序由IIS網(wǎng)站進(jìn)程w3wp.exe內(nèi)部托管)。
為展示ASP.NET Core跨平臺(tái)特性,本文重點(diǎn)著墨經(jīng)典的Out-Process托管模型。
宏觀設(shè)計(jì)
為解耦平臺(tái)web服務(wù)器差異,程序內(nèi)置Http服務(wù)組件Kestrel,由web服務(wù)器轉(zhuǎn)發(fā)請(qǐng)求到Kestrel。
-
老牌web服務(wù)器定位成反向代理服務(wù)器,轉(zhuǎn)發(fā)請(qǐng)求到ASP.NET Core程序(分別由IIS ASP.NET Core Module和Nginx負(fù)責(zé))
常規(guī)代理服務(wù)器,只用于代理內(nèi)部主機(jī)對(duì)外網(wǎng)的連接需求,一般不支持外部對(duì)內(nèi)部網(wǎng)絡(luò)的訪問(wèn)請(qǐng)求; 當(dāng)一個(gè)代理服務(wù)器能夠代理外部網(wǎng)絡(luò)的主機(jī),訪問(wèn)內(nèi)部網(wǎng)絡(luò),這種代理服務(wù)器被稱(chēng)為反向代理服務(wù)器 。
-
平臺(tái)web代理服務(wù)器、ASP.NET Core程序(dotnet.exe) 均為獨(dú)立進(jìn)程,平臺(tái)自行決定互動(dòng)細(xì)節(jié),只需確保平臺(tái)web服務(wù)器與Kestrel形成Http通信。
Kestrel
與老牌web服務(wù)器解耦,實(shí)現(xiàn)跨平臺(tái)部署。
-
Kestrel使ASP.NET Core具備了基本web服務(wù)器的能力,在內(nèi)網(wǎng)部署和開(kāi)發(fā)環(huán)境完全可使用dotnet.exe自宿模式運(yùn)行。
-
Kestrel定位是Http服務(wù)組件,實(shí)力還比不上老牌web服務(wù)器,在timeout機(jī)制、web緩存、響應(yīng)壓縮等不占優(yōu)勢(shì),在安全性等方面還有缺陷。
因此在生產(chǎn)環(huán)境中必須使用老牌web服務(wù)器反向代理請(qǐng)求。
跨平臺(tái)管控程序,轉(zhuǎn)發(fā)請(qǐng)求
要實(shí)現(xiàn)企業(yè)級(jí)穩(wěn)定部署:
*nix平臺(tái)
將ASP.NET Core程序以dotnet.exe自宿模式運(yùn)行,并配置為系統(tǒng)守護(hù)進(jìn)程(管控應(yīng)用),再由Nginx轉(zhuǎn)發(fā)請(qǐng)求。
以下使用systemd創(chuàng)建進(jìn)程服務(wù)文件 /etc/systemd/system/kestrel-eqidproxyserver.service
[Unit]Description=EqidProxyServer deploy on centos[Service]WorkingDirectory=/var/www/eqidproxyserver/eqidproxyServerExecStart=/usr/bin/dotnet /var/www/eqidproxyserver/eqidproxyServer/EqidProxyServer.dllRestart=always# Restart service after 10 seconds if the dotnet service crashes:RestartSec=10TimeoutStopSec=90KillSignal=SIGINTSyslogIdentifier=dotnet-exampleUser=rootEnvironment=ASPNETCORE_ENVIRONMENT=ProductionEnvironment=DOTNET_PRINT_TELEMETRY_MESSAGE=false[Install]WantedBy=multi-user.target
// 啟用服務(wù),在localhost:5000端口偵聽(tīng)請(qǐng)求sudo systemctl enable kestrel-eqidproxyserver.service
安裝Nginx,并配置Nginx轉(zhuǎn)發(fā)請(qǐng)求到localhost:5000:
server {listen 80;server_name default_website;root /usr/share/nginx/html;# Load configuration files for the default server block.include /etc/nginx/default.d/*.conf;location / {proxy_pass http://localhost:5000;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection keep-alive;proxy_set_header Host $host;proxy_cache_bypass $http_upgrade;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}}
windows平臺(tái)
[ 管控應(yīng)用、轉(zhuǎn)發(fā)請(qǐng)求] 由ASP.NET Core Module(插入在IIS Pipeline中的原生組件,下面簡(jiǎn)稱(chēng)ACM)一手操辦,w3wp.exe、dotnet.exe的互動(dòng)關(guān)系是通過(guò)父子進(jìn)程維系。
下圖腳本力證dotnet.exe進(jìn)程是w3wp.exe創(chuàng)建出來(lái)的子進(jìn)程:
得益此關(guān)系,ACM在創(chuàng)建dotnet.exe子進(jìn)程時(shí)能指定環(huán)境變量,約定donet.exe接收(IIS轉(zhuǎn)發(fā)的請(qǐng)求)的偵聽(tīng)端口。
實(shí)際源碼看ACM為子進(jìn)程設(shè)定三個(gè)重要的環(huán)境變量:
-
ASPNETCORE_PORT 約定 Kestrel將會(huì)在此端口上監(jiān)聽(tīng)
-
ASPNETCORE_AppL_PATH
-
ASPNETCORE_TOKEN 約定 攜帶該Token的請(qǐng)求為合法的轉(zhuǎn)發(fā)請(qǐng)求
與ACM夫唱婦隨的是UseIISIntegration擴(kuò)展方法,完成如下工作:
① 啟動(dòng)Kestrel服務(wù)在http://localhost:{ASPNETCORE_PORT}上監(jiān)聽(tīng)
② 根據(jù) {ASPNETCORE_TOKEN} 檢查請(qǐng)求是否來(lái)自ACM轉(zhuǎn)發(fā)
ACM轉(zhuǎn)發(fā)的請(qǐng)求,會(huì)攜帶名為MS-ASPNETCORE-TOKEN:******的Request Header,以便dotnet.exe對(duì)比研判。
③ 利用ForwardedHeaderMiddleware中間件保存原始請(qǐng)求信息
linux平臺(tái)部署需要手動(dòng)啟用ForwardedHeader middleware https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.1
源碼快速驗(yàn)證:
namespace Microsoft.AspNetCore.Hosting{public static class WebHostBuilderIISExtensions{// These are defined as ASPNETCORE_ environment variables by IIS's AspNetCoreModule.private static readonly string ServerPort = "PORT";private static readonly string ServerPath = "APPL_PATH";private static readonly string PairingToken = "TOKEN";private static readonly string IISAuth = "IIS_HTTPAUTH";private static readonly string IISWebSockets = "IIS_WEBSOCKETS_SUPPORTED";/// <summary>/// Configures the port and base path the server should listen on when running behind AspNetCoreModule./// The app will also be configured to capture startup errors.public static IWebHostBuilder UseIISIntegration(this IWebHostBuilder hostBuilder){var port = hostBuilder.GetSetting(ServerPort) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPort}");var path = hostBuilder.GetSetting(ServerPath) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPath}");var pairingToken = hostBuilder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}");var iisAuth = hostBuilder.GetSetting(IISAuth) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISAuth}");var websocketsSupported = hostBuilder.GetSetting(IISWebSockets) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISWebSockets}");bool isWebSocketsSupported;if (!bool.TryParse(websocketsSupported, out isWebSocketsSupported)){// If the websocket support variable is not set, we will always fallback to assuming websockets are enabled.isWebSocketsSupported = (Environment.OSVersion.Version >= new Version(6, 2));}if (!string.IsOrEmpty(port) && !string.IsOrEmpty(path) && !string.IsOrEmpty(pairingToken)){// Set flag to prevent double service configurationhostBuilder.UseSetting(nameof(UseIISIntegration), true.ToString);var enableAuth = false;if (string.IsOrEmpty(iisAuth)){// back compat with older ANCM versionsenableAuth = true;}else{// Lightup a new ANCM variable that tells us if auth is enabled.foreach (var authType in iisAuth.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)){if (!string.Equals(authType, "anonymous", StringComparison.OrdinalIgnoreCase)){enableAuth = true;break;}}}var address = "http://127.0.0.1:" + port;hostBuilder.CaptureStartupErrors(true);hostBuilder.ConfigureServices(services =>{// Delay register the url so users don't accidentally overwrite it.hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, address);hostBuilder.PreferHostingUrls(true);services.AddSingleton<IServerIntegratedAuth>(_ => new ServerIntegratedAuth{IsEnabled = enableAuth,AuthenticationScheme = IISDefaults.AuthenticationScheme});services.AddSingleton<IStartupFilter>(new IISSetupFilter(pairingToken, new PathString(path), isWebSocketsSupported));services.Configure<ForwardedHeadersOptions>(options =>{options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;});services.Configure<IISOptions>(options =>{options.ForwardWindowsAuthentication = enableAuth;});services.AddAuthenticationCore;});}return hostBuilder;}}}
總結(jié)
ASP.NET Core跨平臺(tái)的核心在于 程序內(nèi)置Kestrel HTTP通信組件,解耦web服務(wù)器差異;依平臺(tái)特性約定Http通信細(xì)節(jié)。
本文從框架設(shè)計(jì)初衷、進(jìn)程模型、組件交互驗(yàn)證我對(duì)ASP.NET Core跨平臺(tái)特性的理解。
+ CentOS上部署ASP.NET Core完整版請(qǐng)參考:https://www.cnblogs.com/JulianHuang/p/10455644.html






