你真的了解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服务器上的http请求发送到代理服务器,常规的代理服务器不支持外部对内部网络的访问请求;
当一个代理服务器能够代理外部网络的主机,访问内部网络,这种代理服务器的方式称为反向代理服务器 。
④ Web进程(dotnet.exe)是IIS网站工作进程w3wp.exe 创建出来的子进程, 正因为如此,ASP.NET Core Module对网站工作进程 w3wp.exe 设定的进程内环境变量可以被 dotnet.exe 子进程继承。
验证:
- 任务管理器或 tasklist /fi "imagename eq dotnet.exe" 命令 找到dotnet.exe进程ID:22792
- wmic process where ProcessId=22972 get ParentProcessId 返回父进程ID:8232
- 任务管理器或 tasklist /fi "pid eq 8232" 命令找到 父进程是 w3wp.exe
2. Kestrel: 进程内HTTP服务器
与老牌web服务器解耦,实现跨平台部署
- 进程内Http服务器,ASP.NET Core 保持作为独立Web服务器的能力,可将 ASP.NET Core 网站当可执行程序启动, 在内网部署和开发环境中我们完全可以使用Kestrel来充当web服务器。
- 客观上Kestrel还是作为Http服务器,功能还比不上老牌的web服务器, 可以说在生产环境中要求使用老牌web服务器反向代理请求
Kestrel自诞生之日起还有一些网络安全方面的缺陷,这些缺陷包括一个合适的timeouts,Size limits,和并发数量等
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
组件能力
① 进程管理: 控制web启动进程内Kestrel服务器在某端口上启动,并监听转发请求
② 故障恢复: 控制web在1min内崩溃重启
③ 请求转发
④ 启动日志记录: web启动失败,可通过配置将日志输出到指定目录
⑤ 请求头信息转发:dotnet.exe程序需要收到原始的请求信息
代理服务器转发请求时可能丢失的信息:
- 源IP地址丢失
- scheme:原始请求的scheme:https/http丢失(反向代理服务器和Kestrel之间通过Http交互,并不直接记录原始请求的scheme)
- IIS/nginx等代理服务器可能修改原始请求的Host消息头
⑥ 转发windiws认证token
以上能力,可以参考https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.1
给出的AspNetCore Module配置参数
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
在IIS部署时, UseIISIntegration()会默认为你配置并启用ForwardedHeaderMiddleware 中间件; 在linux平台部署需要你手动启用ForwardedHeader middleware
https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.2
//------------- 节选自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 >= , ));
}
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类 -------------------// Retrieves the value of an environment variable from the current process or from the Windows operating system registry key for the current user or local machine public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target); // Retrieves the value of an environment variable from the current process. public static string GetEnvironmentVariable(string variable);
That's All. 本文期待从框架设计初衷、进程模型、组件作用、组件功能给大家梳理出ASP.NET Core2.1的IIS部署原理。
感谢您的认真阅读,如有问题请大胆斧正,如果您觉得本文对你有用,不妨帮忙点个
或加关注。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置注明本文的作者及原文链接,否则保留追究法律责任的权利。
你真的了解ASP.NET Core 部署模型吗?的更多相关文章
- ASP.NET Boilerplate 学习 AspNet Core2 浏览器缓存使用 c#基础,单线程,跨线程访问和线程带参数 wpf 禁用启用webbroswer右键菜单 EF Core 2.0使用MsSql/MySql实现DB First和Code First ASP.NET Core部署到Windows IIS QRCode.js:使用 JavaScript 生成
ASP.NET Boilerplate 学习 1.在http://www.aspnetboilerplate.com/Templates 网站下载ABP模版 2.解压后打开解决方案,解决方案目录: ...
- ASP.NET CORE部署到Linux
ASP.NET CORE部署到CentOS中 在Linux上安装.NET Core 参考:https://www.microsoft.com/net/core#linuxcentos 配置Nginx ...
- Asp.Net Core部署到Linux服务器
从2016年7月, .NET Core1.0 正式发布开始,由于时间问题,我没怎么关注过.NET Core,最近刚抽出点时间研究了下,先讲下如何把ASP.NET Core部署到Linux上吧.这里我用 ...
- ASP.NET CORE 管道模型及中间件使用解读
说到ASP.NET CORE 管道模型不得不先来看看之前的ASP.NET 的管道模型,两者差异很大,.NET CORE 3.1 后完全重新设计了框架的底层,.net core 3.1 的管道模型更加灵 ...
- ASP.net Core部署说明(Ubuntu) [转]
最近在学习asp.net core,当然学习的目的是想了解一下,Asp.net core是否真的能够是先跨平台部署. 根据目前官网资料说明,asp.net core只有在Redhat 企业版上,才能够 ...
- 又一篇Centos7下的asp.net core部署教程
历程2个多月的学习,我终于从PHP转.Net开发了. 从壹开始前后端分离[ .NETCore2.1 +Vue 2 +AOP+DI]框架 感谢老张的博客,我对asp.net core入门主要就是靠他的博 ...
- asp.net core 部署 提示DataProtectionServices 错误
今天在部署asp.net core网站时,因为调用到阿里云的api,api的参数需要加密签名,系统报出了如下错误: warn: Microsoft.Extensions.DependencyInjec ...
- 从零开始,将ASP.NET Core部署到Linux生产环境
研究.NET Core已经一段时间了,一直都是在Windows上开发,这2天尝试着将公司一个很简单的内部Web项目改造成了ASP.NET Core,并且部署到Linux上.生产环境如下: Linux ...
- Asp.net Core 部署到Azure.cn的一个小问题
前一段尝试在azure.cn上部署Aps.net Core未成功,报503错误!在网上查到是Azure.cn的问题,未能完美支持Asp.net Core! Asp.net Core发表正式版了,又尝试 ...
随机推荐
- 代码审计之SQL注入:BlueCMSv1.6 sp1
Preface 这是一篇纪录关于BlueCMSv1.6 sp1两个SQL注入的审计过程,原文来自代码审计之SQL注入:BlueCMSv1.6 sp1 ,主要纪录一下个人在参考博文复现这两个漏洞经过. ...
- XGBoost算法--学习笔记
学习背景 最近想要学习和实现一下XGBoost算法,原因是最近对项目有些想法,准备做个回归预测.作为当下比较火的回归预测算法,准备直接套用试试效果. 一.基础知识 (1)泰勒公式 泰勒公式是一个用函数 ...
- Java容器:Stack,Queue,PriorityQueue和BlockingQueue
Stack Queue PriorityQueue BlockingQueue ArrayBlockingQueue LinkedBlockingQueue PriorityBlockingQueue ...
- IAAS-libvirt介绍。
Libvirt介绍 Libvirt与hypervisor无关,其提供与多种操作系统虚拟化能力进行交互的API与工具库. Libvirt提供了一个通用稳定的抽象层,可以安全的操作物理机上的虚拟机,同时为 ...
- Oracle12c中分区(Partition)新特性之TRUNCATEPARTITION和EXCHANGE PARTITION级联功能
TRUNCATE [SUB]PARTITION和EXCHANGE [SUB]PARTITION命令如今可以包括CASCADE子句,从而允许参照分区表向下级联这些操作.为确保该选项正常,相关外键也必须包 ...
- python__基础 : 类的__init__,__str__,__del__方法
__init__:当实例化一个类的时候,首相会执行__new__方法创建一个对象,接下来会执行__init__方法对对象的一些属性进行初始化. 所以如果对象有属性,一般会直接写在__init__方法里 ...
- C++ 模板基础
我们学习使用C++,肯定都要了解模板这个概念.就我自己的理解,模板其实就是为复用而生,模板就是实现代码复用机制的一种工具,它可以实现类型参数化,即把类型定义为参数:进而实现了真正的代码可重用性.模版可 ...
- 解决VMware虚拟机报错“无法连接MKS:套接字连接尝试次数太多,正在放弃”
1.错误描述 在VMware中打开虚拟机时报错: "无法连接MKS:套接字连接尝试次数太多,正在放弃" 物理机操作系统: Windows 7 虚拟机操作系统: Kali Linux ...
- HTML学习之制作导航网页
前言 今天用HTML写了一个网址导航,源代码如下: <html> <head> <title>网址导航</title> </head> &l ...
- 你不知道的JavaScript--Item11 arguments对象
1.什么是arguments arguments 是是JavaScript里的一个内置对象,它很古怪,也经常被人所忽视,但实际上是很重要的.所有主要的js函数库都利用了arguments对象.所以ag ...
