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。
-
老牌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服務器反向代理請求。
跨平臺管控程序,轉發請求
要實現企業級穩定部署:
*nix平臺
將ASP.NET Core程序以dotnet.exe自宿模式運行,并配置為系統守護進程(管控應用),再由Nginx轉發請求。
以下使用systemd創建進程服務文件 /etc/systemd/system/kestrel-eqidproxyserver.service
[Unit]
Description=EqidProxyServer deploy on centos
[Service]
WorkingDirectory=/var/www/eqidproxyserver/eqidproxyServer
ExecStart=/usr/bin/dotnet /var/www/eqidproxyserver/eqidproxyServer/EqidProxyServer.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
TimeoutStopSec=90
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=root
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
// 啟用服務,在localhost:5000端口偵聽請求
sudo systemctl enable kestrel-eqidproxyserver.service
安裝Nginx,并配置Nginx轉發請求到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平臺
[ 管控應用、轉發請求] 由ASP.NET Core Module(插入在IIS Pipeline中的原生組件,下面簡稱ACM)一手操辦,w3wp.exe、dotnet.exe的互動關系是通過父子進程維系。
下圖腳本力證dotnet.exe進程是w3wp.exe創建出來的子進程:
得益此關系,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 middleware https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.1
源碼快速驗證:
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 configuration
hostBuilder.UseSetting(nameof(UseIISIntegration), true.ToString);
var enableAuth = false;
if (string.IsOrEmpty(iisAuth))
{
// back compat with older ANCM versions
enableAuth = 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;
}
}
}
總結
ASP.NET Core跨平臺的核心在于 程序內置Kestrel HTTP通信組件,解耦web服務器差異;依平臺特性約定Http通信細節。
本文從框架設計初衷、進程模型、組件交互驗證我對ASP.NET Core跨平臺特性的理解。
+ CentOS上部署ASP.NET Core完整版請參考:https://www.cnblogs.com/JulianHuang/p/10455644.html