ASP.NET Core跨平臺技術內幕

ASP.NET Core設計初衷是開源跨平臺、高性能Web服務器,其中跨平臺特性較早期ASP.NET是一個顯著的飛躍,.NET現可以理直氣壯與JAVA同臺競技,而ASP.NET Core的高性能特性更是成為致勝法寶。

ASP.NET Core 2.1+為IIS託管新增In-Process模型並作為默認選項(使用IISHttpServer替代了Kestrel,dotnet程序由IIS網站進程w3wp.exe內部託管)。

為展示ASP.NET Core跨平臺特性,本文重點著墨經典的Out-Process託管模型

宏觀設計

為解耦平臺web服務器差異,程序內置Http服務組件Kestrel,由web服務器轉發請求到Kestrel。

ASP.NET Core跨平台技术内幕
  • 老牌web服務器定位成反向代理服務器,轉發請求到ASP.NET Core程序(分別由IIS ASP.NET Core Module和Nginx負責)

常規代理服務器,只用於代理內部主機對外網的連接需求,一般不支持外部對內部網絡的訪問請求; 當一個代理服務器能夠代理外部網絡的主機,訪問內部網絡,這種代理服務器被稱為反向代理服務器 。

  • 平臺web代理服務器、ASP.NET Core程序(dotnet.exe) 均為獨立進程,平臺自行決定互動細節,只需確保平臺web服務器與Kestrel形成Http通信。

Kestrel

與老牌web服務器解耦,實現跨平臺部署。

  • Kestrel使ASP.NET Core具備了基本web服務器的能力,在內網部署和開發環境完全可使用dotnet.exe自宿模式運行。

  • Kestrel定位是Http服務組件,實力還比不上老牌web服務器,在timeout機制、web緩存、響應壓縮等不佔優勢,在安全性等方面還有缺陷。

因此在生產環境中必須使用老牌web服務器反向代理請求。

跨平臺管控程序,轉發請求

要實現企業級穩定部署:

ASP.NET Core跨平台技术内幕

*nix平臺

將ASP.NET Core程序以dotnet.exe自宿模式運行,並配置為系統守護進程(管控應用),再由Nginx轉發請求。

以下使用systemd創建進程服務文件 /etc/systemd/system/kestrel-eqidproxyserver.service

<code>[Unit]/<code><code>Description=EqidProxyServer deploy on centos/<code>
<code>[Service]/<code><code>WorkingDirectory=/var/www/eqidproxyserver/eqidproxyServer/<code><code>ExecStart=/usr/bin/dotnet /var/www/eqidproxyserver/eqidproxyServer/EqidProxyServer.dll/<code><code>Restart=always/<code><code># Restart service after 10 seconds if the dotnet service crashes:/<code><code>RestartSec=10/<code><code>TimeoutStopSec=90/<code><code>KillSignal=SIGINT/<code><code>SyslogIdentifier=dotnet-example/<code><code>User=root/<code><code>Environment=ASPNETCORE_ENVIRONMENT=Production/<code><code>Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false/<code>
<code>[Install]/<code><code>WantedBy=multi-user.target/<code>
<code>// 啟用服務,在localhost:5000端口偵聽請求/<code><code>sudo systemctl enable kestrel-eqidproxyserver.service /<code>

安裝Nginx,並配置Nginx轉發請求到localhost:5000:

<code>server {/<code><code> listen 80;/<code><code> server_name default_website;/<code><code> root /usr/share/nginx/html;/<code>
<code> # Load configuration files for the default server block./<code><code> include /etc/nginx/default.d/*.conf;/<code>
<code> location / {/<code><code> proxy_pass http://localhost:5000;/<code><code> proxy_http_version 1.1;/<code><code> proxy_set_header Upgrade $http_upgrade;/<code><code> proxy_set_header Connection keep-alive;/<code><code> proxy_set_header Host $host;/<code><code> proxy_cache_bypass $http_upgrade;/<code><code> proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;/<code><code> proxy_set_header X-Forwarded-Proto $scheme;/<code><code> }/<code><code> }/<code>

Windows平臺

[ 管控應用、轉發請求] 由ASP.NET Core Module(插入在IIS Pipeline中的原生組件,下面簡稱ACM)一手操辦,w3wp.exe、dotnet.exe的互動關係是通過父子進程維繫。

下圖腳本力證dotnet.exe進程是w3wp.exe創建出來的子進程:

ASP.NET Core跨平台技术内幕

得益此關係,ACM在創建dotnet.exe子進程時能指定環境變量,約定donet.exe接收(IIS轉發的請求)的偵聽端口。

實際源碼看ACM為子進程設定三個重要的環境變量:

  • ASPNETCORE_PORT 約定 Kestrel將會在此端口上監聽

  • ASPNETCORE_APPL_PATH

  • ASPNETCORE_TOKEN 約定 攜帶該Token的請求為合法的轉發請求

與ACM夫唱婦隨的是UseIISIntegration擴展方法,完成如下工作:

① 啟動Kestrel服務在http://localhost:{ASPNETCORE_PORT}上監聽

② 根據 {ASPNETCORE_TOKEN} 檢查請求是否來自ACM轉發

ACM轉發的請求,會攜帶名為MS-ASPNETCORE-TOKEN:******的Request Header,以便dotnet.exe對比研判。

③ 利用ForwardedHeaderMiddleware中間件保存原始請求信息

linux平臺部署需要手動啟用ForwardedHeader mi

ddleware https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.1

源碼快速驗證:

<code>namespace Microsoft.AspNetCore.Hosting/<code><code>{/<code><code> public static class WebHostBuilderIISExtensions/<code><code> {/<code><code> // These are defined as ASPNETCORE_ environment variables by IIS's AspNetCoreModule./<code><code> private static readonly string ServerPort = "PORT";/<code><code> private static readonly string ServerPath = "APPL_PATH";/<code><code> private static readonly string PairingToken = "TOKEN";/<code><code> private static readonly string IISAuth = "IIS_HTTPAUTH";/<code><code> private static readonly string IISWebSockets = "IIS_WEBSOCKETS_SUPPORTED";/<code>
<code> /// <summary>/<code><code> /// Configures the port and base path the server should listen on when running behind AspNetCoreModule./<code><code> /// The app will also be configured to capture startup errors./<code><code> public static IWebHostBuilder UseIISIntegration(this IWebHostBuilder hostBuilder)/<code><code> {/<code><code> var port = hostBuilder.GetSetting(ServerPort) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPort}");/<code><code> var path = hostBuilder.GetSetting(ServerPath) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPath}");/<code><code> var pairingToken = hostBuilder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}");/<code><code> var iisAuth = hostBuilder.GetSetting(IISAuth) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISAuth}");/<code><code> var websocketsSupported = hostBuilder.GetSetting(IISWebSockets) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISWebSockets}");/<code>
<code> bool isWebSocketsSupported;/<code><code> if (!bool.TryParse(websocketsSupported, out isWebSocketsSupported))/<code><code> {/<code><code> // If the websocket support variable is not set, we will always fallback to assuming websockets are enabled./<code><code> isWebSocketsSupported = (Environment.OSVersion.Version >= new Version(6, 2));/<code><code> }/<code>
<code> if (!string.IsOrEmpty(port) && !string.IsOrEmpty(path) && !string.IsOrEmpty(pairingToken))/<code><code> {/<code><code> // Set flag to prevent double service configuration/<code><code> hostBuilder.UseSetting(nameof(UseIISIntegration), true.ToString);/<code>
<code> var enableAuth = false;/<code><code> if (string.IsOrEmpty(iisAuth))/<code><code> {/<code><code> // back compat with older ANCM versions/<code><code> enableAuth = true;/<code><code> }/<code><code> else/<code><code> {/<code><code> // Lightup a new ANCM variable that tells us if auth is enabled./<code><code> foreach (var authType in iisAuth.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))/<code><code> {/<code><code> if (!string.Equals(authType, "anonymous", StringComparison.OrdinalIgnoreCase))/<code><code> {/<code><code> enableAuth = true;/<code><code> break;/<code><code> }/<code><code> }/<code><code> }/<code>
<code> var address = "http://127.0.0.1:" + port;/<code><code> hostBuilder.CaptureStartupErrors(true);/<code><code> hostBuilder.ConfigureServices(services =>/<code><code> {/<code><code> // Delay register the url so users don't accidentally overwrite it./<code><code> hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, address);/<code><code> hostBuilder.PreferHostingUrls(true);/<code><code> services.AddSingleton<iserverintegratedauth>(_ => new ServerIntegratedAuth/<iserverintegratedauth>/<code><code> {/<code><code> IsEnabled = enableAuth,/<code><code> AuthenticationScheme = IISDefaults.AuthenticationScheme/<code><code> });/<code><code> services.AddSingleton<istartupfilter>(new IISSetupFilter(pairingToken, new PathString(path), isWebSocketsSupported));/<istartupfilter>/<code><code> services.Configure<forwardedheadersoptions>(options =>/<forwardedheadersoptions>/<code><code> {/<code><code> options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;/<code><code> });/<code><code> services.Configure<iisoptions>(options =>/<iisoptions>/<code><code> {/<code><code> options.ForwardWindowsAuthentication = enableAuth;/<code><code> });/<code><code> services.AddAuthenticationCore;/<code><code> });/<code><code> }/<code><code> return hostBuilder;/<code><code> }/<code><code> }/<code><code>}/<code>

總結

ASP.NET Core跨平臺的核心在於 程序內置Kestrel HTTP通信組件,解耦web服務器差異;依平臺特性約定Http通信細節。

本文從框架設計初衷、進程模型、組件交互驗證我對ASP.NET Core跨平臺特性的理解。

+ CentOS上部署ASP.NET Core完整版請參考:https://www.cnblogs.com/JulianHuang/p/10455644.html

讓乾貨飛一會。

............


分享到:


相關文章: