深度理解IIS下部署ASP.NET Core2.1 Web应用拓扑图

烂漫一生 提交于 2020-04-25 06:59:37
原文: 深度理解IIS下部署ASP.NET Core2.1 Web应用拓扑图

IIS部署ASP.NET Core2.1 应用拓扑图

 

我们看到相比Asp.Net, 出现了3个新的组件:ASP.NET Core Module、Kestrel、dotnet.exe, 后面我们会理清楚这三个组件的作用和组件之间的交互原理。

 

引入Kestrel的原因  

进程内HTTP服务器,与老牌web服务器解耦,实现跨平台部署

IIS、Nginx、Apache等老牌web服务器有他们自己的启动进程和环境;为了实现跨平台部署,需要与这些web服务器的功能解耦,网络通信是一个比较好的选择。

作为进程内Http服务器,ASP.NET Core 保持独立运作一个Http服务器的能力,由这些老牌web服务器充当反向代理服务器将请求转发给进程内Http服务器,这样保持了开发过程中配置Startup、Program文件的纯粹性 和部署时候的跨平台能力。

客观上Kestrel还是作为Http服务器,功能还比不上老牌的web服务器

  Kestrel自诞生之日起还有一些网络安全方面的缺陷,这些缺陷包括一个合适的timeouts,Size limits,和并发数量等

 
也就是说从主观和客观上都要求生产部署使用反向代理服务器
 

在内网部署和开发环境中我们完全可以使用Kestrel来充当web服务器。

Kestrel的底层实现细节:Kestrel 要做到跨平台HTTP服务器,需要脱离底层系统细节实现跨平台IO,在Asp.NetCore2.1 版本之前使用libuv(一个高性能跨平台IO库),2.1 版本之后采用的托管Sockets,这是一个巨大的变化,有兴趣的可以搜索一下。

 

引入ASP.NET Core Module

       反向代理服务器的作用是将请求转发给内网的Http服务器,IIS上使用ASP.NET Core Module组件将请求转发到Kestrel Http服务器; 注意该组件只在IIS上有效。

从整个拓扑图上看,请求首先到达内核态Http.sys Driver,该驱动将请求路由到IIS上指定网站;

然后Asp.Net Core Module将请求转发给Kestrel服务器。

 

作为企业级服务ASP.NET Core Module需要完成:

  • 进程管理: 控制webApp启动进程内Kestrel服务器在某端口上启动,并监听转发请求

  • 故障恢复: 控制webapp在1min内崩溃重启的次数

  • 请求转发

  • 启动日志记录: webapp启动失败,可通过配置将日志输出到指定目录

  • 请求头信息转发:代理服务器需要保证传递 源IP地址、源Scheme、原始Host请求头等信息

  • 转发windiws认证token

以上能力,可以参考https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.1
给出的AspNetCore Module配置参数

 

自然可以猜想ASP.NET Core Module与IISIntegration()关系很是密切:

  1. Web启动的时候,ASP.NET Core Module会通过环境变量指定kestrel监听的端口;

  2. IIS Integration方法则配置服务器在http://localhost:{指定端口}上监听。

  3. 同时开始检查请求是否来自AspNet Core Module(非ASPNE TCore Module转发的请求会被拒绝)  

          另外由于反向代理服务器的存在,webapp可能会丢失原始请求信息; 比如

源IP地址

-  scheme: 反向代理服务器和Kestrel之间通过Http交互

-  可能被代理服务器修改的原始Host请求头

这些工作由ForwardedHeader middleware完成,该中间件由UseIISIntegration方法默认开启。

 薅一下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;
        }
    }

 可以看到Web程序在配置启动时读取了5个由AspNET Core Module设置的环境变量。

着重理解Kestrel是怎么拒绝来自非AspNetCore Module转发的请求:

  1. AspNetCore Module 设置TOKEN=XXX环境变量

  2. WebApp启动时读取Token=XXX环境变量

  3. AspNetCore Module在转发请求时,会在Request里面加上一个 MS-ASPNETCORE-TOKEN:XXX的请求头

  4. IISMiddleware中间件将会起作用:携带了该请求头的转发请求被认为有效

//---------------节选自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;
      }
      ......
}
  20181205 更新,.NetCore2.2+ ASP.NETCore Module支持进程内托管模型,本文章内容针对AspNetCore2.1
 

FAQ:

1. 什么叫反向代理服务器Reverse Proxy Server?

通常的代理服务器,只用于代理内部网络对Internet的连接需求,客户机必须指定代理服务器将本来要直接发送到web服务器上的http请求发送到代理服务器中,普通的代理服务器不支持外部对内部网络的访问请求;

当一个代理服务器能够代理外部网络的主机,访问内部网络,这种代理服务器的方式称为反向代理服务器 。

在AspNet Core Web应用IIS、Ngnix转发请求时,内部的Kestrel和Applicaiton Code组成完整的内部网络,IIS、Nginx等web服务器在这里的角色就起到了反向代理作用。

 

2. 依照上图的拓扑图我们理所当然可以认为背靠在IIS后面的Kestrel应该是可以访问的,因为它也是一个web服务器,那么怎样直接访问背靠在AspNetCore Module后面的的Kestrel服务器?

按照上文的理论,背靠在AspNetCore Module后面的Web进程是依靠 Token来拒绝【非AspNetCore Module转发的请求】。

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.2

因此,将该PairToken拷贝到请求头,我们也是可以在Kestrel behind IIS的情况下,直接访问Kestrel(虽然这种情况不常见,但是对于我们理解拓扑图很有帮助)。

步骤如下:

  1.  找到dotnet进程: tasklist | findstr "dotnet"

  2. 找到该进程占用port : netstat -ano | findstr {pid}

  3. 利用输出的port: curl localhost:{port}, 会提示400 badrequest, 这与源码的返回一致

  4. 从error log 中拷贝出该MS-ASPNETCORE-TOKEN, 附在request header中便可以访问

 

 --------------如果你觉得文章对你有价值,请点赞或者关注,支持原创势力,蟹蟹--------------~~。。~~--------------~~。。~~----------------

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!