前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ASP.NET Core跨平台技术内幕

ASP.NET Core跨平台技术内幕

作者头像
有态度的马甲
发布2020-04-16 14:59:18
1.1K0
发布2020-04-16 14:59:18
举报
文章被收录于专栏:精益码农精益码农

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

代码语言:javascript
复制
[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
代码语言:javascript
复制
// 启用服务,在localhost:5000端口侦听请求
sudo systemctl enable kestrel-eqidproxyserver.service   

安装Nginx,并配置Nginx转发请求到localhost:5000:

代码语言:javascript
复制
代码语言:javascript
复制
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

源码快速验证:

代码语言:javascript
复制
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.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 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服务器差异。

本文从框架设计初衷、进程模型、组件交互验证我对ASP.NET Core跨平台特性的理解。

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 精益码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档