你真的了解ASP.NET Core 部署模型吗?
原文:
你真的了解ASP.NET Core 部署模型吗?
? ----------------------------???以下内容针对 ASP.NET Core2.1,2.2出现IIS进程内寄宿 暂不展开讨论-------------------------- ?
? ? ? ? ? 相比ASP.NET,ASP.NET Core 2.1出现了3个新的组件:ASP.NET Core Module、Kestrel、dotnet.exe, 后面我们会理清楚这三个组件的作用和组件之间的交互原理。? ?ASP.NET Core 设计的初衷是开源跨平台、高性能Web服务器,ASP.NET Core跨平台特性相对于早期ASP.NET 是一个显著的飞跃,.NET程序可以理直气壮与JAVA同台竞技,而ASP.NET Core的高性能特性更是成为致胜法宝。 ? 1. ASP.NET Core宏观梳理?为实现跨平台部署.Net程序,微软为ASP.NET Core重新梳理了部署架构: ? ? ? ? ① 由于各平台都有特定web服务器, 为解耦差异,采用HTTP通信的方式,将web服务器的请求转发到 ASP.NET Core 程序处理? ? ? ? ? ②?ASP.NET Core Web进程(dotnet.exe)会使用一个进程内HTTP服务器:Kestrel, 处理转发过来的请求? ? ? ? ? ③ Web服务器现在定位成 反向代理服务器, ASP.NET Core? Module组件负责转发请求到内网Kestrel服务器
? ? ? ? ④ Web进程(dotnet.exe)是IIS网站工作进程w3wp.exe 创建出来的子进程, 正因为如此,ASP.NET Core Module对网站工作进程 w3wp.exe 设定的进程内环境变量可以被 dotnet.exe 子进程继承。??
? 2. Kestrel: 进程内HTTP服务器? ? ? ? ?与老牌web服务器解耦,实现跨平台部署 -? 进程内Http服务器,ASP.NET Core 保持作为独立Web服务器的能力,可将 ASP.NET Core 网站当可执行程序启动,?在内网部署和开发环境中我们完全可以使用Kestrel来充当web服务器。 -? 客观上Kestrel还是作为Http服务器,功能还比不上老牌的web服务器,? 可以说在生产环境中要求使用老牌web服务器反向代理请求
? ??
3. ASP.NET Core Module? ? ? ? ?反向代理服务器的作用是将请求转发给内网的Http服务器,IIS上使用ASP.NET Core Module组件将请求转发到Kestrel Http服务器(注意该组件只在IIS上有效)。 ?从整个拓扑图上看,请求首先到达内核态Http.sys Driver,该驱动将请求路由到IIS上指定网站;然后Asp.Net Core Module将请求转发给Kestrel服务器。 ?3.1??组件能力
作为企业级转发组件ASP.NET Core Module需要完成:
? ? ①?进程管理: 控制web启动进程内Kestrel服务器在某端口上启动,并监听转发请求 ? ? ②?故障恢复: 控制web在1min内崩溃重启? ? ? ③?请求转发 ? ? ④?启动日志记录: web启动失败,可通过配置将日志输出到指定目录? ? ? ⑤?请求头信息转发:dotnet.exe程序需要收到原始的请求信息
? ? ?⑥ 转发windiws认证token
? ?3.2??ASP.NET Core Module组件与dotnet.exe 进程结合? ? ? ??自然可以猜想ASP.NET Core Module与UseIISIntegration()关系很密切: ? ? ? - Web启动的时候,ASP.NET Core Module会通过进程内环境变量指定kestrel监听的端口 ? ? ? -?UseIISIntegration() 拿到环境变量进行一系列配置: ? ? ? ? ? ?① 服务器在http://localhost:{指定端口}上监听 ? ? ? ? ? ?② 根据 token检查请求是否来自AspNet Core Module(非ASPNE TCore Module转发的请求会被拒绝)? ? ? ? ? ? ?③ 保持原始请求信息 :利用ForwardedHeaderMiddleware中间件保存原始请求信息,存储在Header
? ?
? ? ? ?通过 UseIISIntegration() 源码快速验证:
//------------- 节选自Microsoft.AspNetCore.Hosting.WebHostBuilderIISExtensions---------------------
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. /// </summary>
/// <param name="hostBuilder"></param>
/// <returns></returns>
public static IWebHostBuilder UseIISIntegration(this IWebHostBuilder hostBuilder) { if (hostBuilder == null) { throw new ArgumentNullException(nameof(hostBuilder)); } // Check if `UseIISIntegration` was called already
if (hostBuilder.GetSetting(nameof(UseIISIntegration)) != null) { return 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.IsNullOrEmpty(port) && !string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(pairingToken)) { // Set flag to prevent double service configuration
hostBuilder.UseSetting(nameof(UseIISIntegration),true.ToString()); var enableAuth = false; if (string.IsNullOrEmpty(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 accidently overwrite it.
hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey,address); hostBuilder.PreferHostingUrls(true); 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程序生成源码: //---------------------------------节选自Microsoft.AspNetCore.Hosting.Internal.WebHost------------------------------------
private RequestDelegate BuildApplication() { try { _applicationServicesException?.Throw(); EnsureServer(); var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>(); var builder = builderFactory.CreateBuilder(Server.Features); builder.ApplicationServices = _applicationServices; var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>(); Action<IApplicationBuilder> configure = _startup.Configure; foreach (var filter in startupFilters.Reverse()) { configure = filter.Configure(configure); // 挨个启动功能 } configure(builder); return builder.Build(); } ...... }
? ? ? ? IISSetupFilter?内容: //---------------------------------节选自Microsoft.AspNetCore.Server.IISIntegration.IISSetupFilter------------------------------------
namespace Microsoft.AspNetCore.Server.IISIntegration { internal class IISSetupFilter : IStartupFilter { private readonly string _pairingToken; private readonly PathString _pathBase; private readonly bool _isWebsocketsSupported; internal IISSetupFilter(string pairingToken,PathString pathBase,bool isWebsocketsSupported) { _pairingToken = pairingToken; _pathBase = pathBase; _isWebsocketsSupported = isWebsocketsSupported; } public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { return app => { app.UsePathBase(_pathBase); app.UseForwardedHeaders(); // 转发时保持原始请求,放在header里面传给kestrel app.UseMiddleware<IISMiddleware>(_pairingToken,_isWebsocketsSupported); // 阻止非aspnetcore module转发的请求 next(app); }; } } }
? ?? ? 着重理解下UseIISIntegration第②点配置:?怎样拒绝非ASP. NET Core Module 转发的请求? ? ? ??①?AspNetCore Module 为w3wp.exe 工作进程设置进程内环境变量?ASPNETCORE_TOKEN=****** ? ? ? ② dotnet.exe进程继承了父进程?ASPNETCORE_TOKEN=****** 环境变量 ? ? ? ③?AspNetCore Module转发请求到kestrel时,会在Request里面加上一个 MS-ASPNETCORE-TOKEN:****** 的请求头;非AspNetCore Module自然没有该请求头? ? ? ? ?④?IISMiddleware中间件:请求头中匹配该ASPNETCORE_TOKEN=******的请求是有效的。 //---------------节选自Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware----------------------
public async Task Invoke(HttpContext httpContext) { if (!string.Equals(_pairingToken,httpContext.Request.Headers[MSAspNetCoreToken],StringComparison.Ordinal)) { _logger.LogError($"‘{MSAspNetCoreToken}‘ does not match the expected pairing token ‘{_pairingToken}‘,request rejected."); httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; return; } ...... }
? ? ?附:部署在IIS后面的Kestrel 也是一个web服务器,怎样Hack访问搭配ASP.NET Core Module的Kestrel服务器?? ? ? ? 按照上文的理论,部署在IIS后面的dotnet.exe程序是依靠 AspNetCore Module 设定的进程内环境变量ASPNETCORE-TOKEN来识别【非AspNetCore Module转发的请求】。 因此,理论上将该PairToken拷贝到请求头,可访问部署在IIS后面的Kestrel 服务器(这是一个hack行为,对于理解部署图很有帮助)。 操作方式如下: ? ?① 在任务管理器中找到你要分析的dotnet进程,tasklist? /fi "imagename eq dotnet.exe" ,找到要分析{ pid } ? ?② 找到该进程占用port : netstat -ano | findstr {pid} ? ?③?利用输出的port: curl localhost:{port}? --verbose:? 会提示400 badrequest,这与源码的返回一致? ? ?④ 从error log 中拷贝出该环境变量:ASPNETCORE_TOKEN ‘MS-ASPNETCORE-TOKEN‘ does not match the expected pairing token ‘4cdaf1fd-66d5-4b64-b05f-db6cb8d5ebe5‘,request rejected. ? ? ⑤?在request中添加?MS-ASPNETCORE-TOKEN:****** 请求头 ? 【实际上 ,可以在ASP.NET Core dotnet.exe程序内写日志输出 ASPNETCORE_TOKEN?环境变量值。】 //---------------------截取自 System.Environment类 -------------------
时间:2019-04-03 09:02:22
阅读(20)
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- asp.net – 访问IIS7 404重定向页面中的原始URL
- .net – MVC中的Pdf Viewer在View中显示pdf内容
- asp.net – CLSID为{00024500-0000-0000-C000-000000000046
- asp.net – 为什么Visual Studio会为您提供WebForms的Page_
- ASP.NET MVC – 以编程方式添加动作过滤器
- asp.net – GridView:选择页面按钮(数字)样式
- asp.net – 为customvalidator设置errormessage?
- asp.net – WCF:是否有一个属性要在OperationContract中生
- 使用UpdatePanel单击按钮后更新ASP.NET标签
- 处理ASP.Net MVC中的过期标题
- asp.net-mvc-3 – PrepareResponse().AsActionRe
- asp.net-mvc – ASP.net MVC路由参数异常
- asp.net-mvc-4 – 如何开发一个ASP.NET Web API接
- asp.net – Thread.CurrentPrincipal在使用WebGe
- asp.net -fb-like一段时间后用fb-share插件消失了
- asp.net – 如何在网格中按日期升序?
- asp.net-mvc – 有没有更好的方法来使用MVC 4和E
- asp.net – 弹出窗口,如何在IE8中隐藏URL栏
- HTTP 错误 404.3 - Not Found 由于扩展配置问题而
- 巧用Mono.Cecil反射加载类型和方法信息