• .NET Core开发日志——从ASP.NET Core Module到KestrelServer


    ASP.NET Core程序现在变得如同控制台(Console)程序一般,同样通过Main方法启动整个应用。而Main方法要做的事情很简单,创建一个WebHostBuilder类,调用其Build方法生成一个WebHost类,最后启动之。

    实现代码一目了然:

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }
    
        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
    

    要想探寻其内部究竟做了哪些操作,则需要调查下WebHost类中CreateDefaultBuilder静态方法:

    public static IWebHostBuilder CreateDefaultBuilder(string[] args)
    {
        var builder = new WebHostBuilder();
    
        if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
        {
            builder.UseContentRoot(Directory.GetCurrentDirectory());
        }
        if (args != null)
        {
            builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
        }
    
        builder.UseKestrel((builderContext, options) =>
            {
                options.Configure(builderContext.Configuration.GetSection("Kestrel"));
            })
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                var env = hostingContext.HostingEnvironment;
    
                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
    
                if (env.IsDevelopment())
                {
                    var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                    if (appAssembly != null)
                    {
                        config.AddUserSecrets(appAssembly, optional: true);
                    }
                }
    
                config.AddEnvironmentVariables();
    
                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
                logging.AddDebug();
            })
            .ConfigureServices((hostingContext, services) =>
            {
                // Fallback
                services.PostConfigure<HostFilteringOptions>(options =>
                {
                    if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
                    {
                        // "AllowedHosts": "localhost;127.0.0.1;[::1]"
                        var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                        // Fall back to "*" to disable.
                        options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
                    }
                });
                // Change notification
                services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
                    new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
    
                services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
            })
            .UseIISIntegration()
            .UseDefaultServiceProvider((context, options) =>
            {
                options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
            });
    
        return builder;
    }
    

    代码稍微有点多,但这里只关心WebHostBuilder类的创建,以及该builder使用了UseKestrel方法。

    UseKestrel方法内部通过IoC的方式注入了KestrelServer类:

    public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
    {
        return hostBuilder.ConfigureServices(services =>
        {
            // Don't override an already-configured transport
            services.TryAddSingleton<ITransportFactory, SocketTransportFactory>();
    
            services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
            services.AddSingleton<IServer, KestrelServer>();
        });
    }
    

    由此可以知道当一个ASP.NET Core应用程序运行起来时,其内部会有KestrelServer。

    那么为什么会需要这个KestrelServer?因为它可以做为一个反向代理服务器,帮助ASP.NET Core实现跨平台的需要。

    以传统Windows系统上的IIS为例,如下图所示,ASP.NET Core应用程序中的代码已经不再直接依赖于IIS容器,而是通过KestrelServer这个代理将HTTP请求转换为HttpContext对象,再对此对象进行处理。

    图中的ASP.NET Core Module也是由ASP.NET Core的诞生而引入的新的IIS模块。它的主要功能是将Web请求重定向至ASP.NET Core应用程序。并且由于ASP.NET Core应用程序独立运行于IIS工作进程之外的进程,它还负责对进程的管理。

    ASP.NET Core Module的源码由C++编写,入口是main文件中的RegisterModule函数。

    其函数内部实例化了CProxyModuleFactory工厂类。

    pFactory = new CProxyModuleFactory;
    

    而由这个工厂类创建的CProxyModule实例中有一个关键的CProxyModule::OnExecuteRequestHandler方法。它会创建FORWARDING_HANDLER实例,并调用其OnExecuteRequestHandler方法。

    __override
    REQUEST_NOTIFICATION_STATUS
    CProxyModule::OnExecuteRequestHandler(
        IHttpContext *          pHttpContext,
        IHttpEventProvider *
    )
    {
        m_pHandler = new FORWARDING_HANDLER(pHttpContext);
        if (m_pHandler == NULL)
        {
            pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_OUTOFMEMORY);
            return RQ_NOTIFICATION_FINISH_REQUEST;
        }
    
        return m_pHandler->OnExecuteRequestHandler();
    }
    

    在此方法里就有那些核心的处理HTTP请求的操作。

    // 实例化应用程序管理器
    pApplicationManager = APPLICATION_MANAGER::GetInstance();
    
    // 取得应用程序实例
    hr = pApplicationManager->GetApplication(m_pW3Context, &m_pApplication);
    
    // 取得该应用程序的进程
    hr = m_pApplication->GetProcess(m_pW3Context, pAspNetCoreConfig, &pServerProcess);
    
    // 创建HTTP请求
    hr = CreateWinHttpRequest(pRequest,
            pProtocol,
            hConnect,
            &struEscapedUrl,
            pAspNetCoreConfig,
            pServerProcess);
    
    //  发送HTTP请求
    if (!WinHttpSendRequest(m_hRequest,
        m_pszHeaders,
        m_cchHeaders,
        NULL,
        0,
        cbContentLength,
        reinterpret_cast<DWORD_PTR>(static_cast<PVOID>(this))))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO,
            "FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed");
        goto Failure;
    }
    

    在ASP.NET Core应用程序这端,CreateWebHostBuilder(args).Build().Run();代码执行之后,会调用其对应的异步方法:

    private static async Task RunAsync(this IWebHost host, CancellationToken token, string shutdownMessage)
    {
        using (host)
        {
            await host.StartAsync(token);
    
            var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
            var options = host.Services.GetRequiredService<WebHostOptions>();
    
            if (!options.SuppressStatusMessages)
            {
                Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}");
                Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}");
    
    
                var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses;
                if (serverAddresses != null)
                {
                    foreach (var address in serverAddresses)
                    {
                        Console.WriteLine($"Now listening on: {address}");
                    }
                }
    
                if (!string.IsNullOrEmpty(shutdownMessage))
                {
                    Console.WriteLine(shutdownMessage);
                }
            }
    
            await host.WaitForTokenShutdownAsync(token);
        }
    }
    

    该方法中又调用了WebHost的StartAsync方法:

    public virtual async Task StartAsync(CancellationToken cancellationToken = default)
    {
        HostingEventSource.Log.HostStart();
        _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
        _logger.Starting();
    
        var application = BuildApplication();
    
        _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
        _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
        var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
        var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
        var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
        await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
    
        // Fire IApplicationLifetime.Started
        _applicationLifetime?.NotifyStarted();
    
        // Fire IHostedService.Start
        await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
    
        _logger.Started();
    
        // Log the fact that we did load hosting startup assemblies.
        if (_logger.IsEnabled(LogLevel.Debug))
        {
            foreach (var assembly in _options.GetFinalHostingStartupAssemblies())
            {
                _logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly);
            }
        }
    
        if (_hostingStartupErrors != null)
        {
            foreach (var exception in _hostingStartupErrors.InnerExceptions)
            {
                _logger.HostingStartupAssemblyError(exception);
            }
        }
    }
    

    BuildApplication方法内部从IoC容器取出KestrelServer的实例:

    private void EnsureServer()
    {
        if (Server == null)
        {
            Server = _applicationServices.GetRequiredService<IServer>();
    
            var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
            var addresses = serverAddressesFeature?.Addresses;
            if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
            {
                var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey];
                if (!string.IsNullOrEmpty(urls))
                {
                    serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey);
    
                    foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        addresses.Add(value);
                    }
                }
            }
        }
    }
    

    最后调用KestrelServer的StartAsync方法:

    public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
    {
        try
        {
            if (!BitConverter.IsLittleEndian)
            {
                throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
            }
    
            ValidateOptions();
    
            if (_hasStarted)
            {
                // The server has already started and/or has not been cleaned up yet
                throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
            }
            _hasStarted = true;
            _heartbeat.Start();
    
            async Task OnBind(ListenOptions endpoint)
            {
                // Add the HTTP middleware as the terminal connection middleware
                endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application, endpoint.Protocols);
    
                var connectionDelegate = endpoint.Build();
    
                // Add the connection limit middleware
                if (Options.Limits.MaxConcurrentConnections.HasValue)
                {
                    connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
                }
    
                var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
                var transport = _transportFactory.Create(endpoint, connectionDispatcher);
                _transports.Add(transport);
    
                await transport.BindAsync().ConfigureAwait(false);
            }
    
            await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            Trace.LogCritical(0, ex, "Unable to start Kestrel.");
            Dispose();
            throw;
        }
    }
    

    到了这一步,KestrelServer终于可以监听来自ASP.NET Core Module发出的HTTP请求,而ASP.NET Core应用程序也可以开始其自身的任务处理了。

  • 相关阅读:
    bzoj1951 [Sdoi2010]古代猪文
    bzoj2693 jzptab
    数学一本通第三章总结
    poj1019 Number Sequence
    SGU179 Brackets light
    字母组合2
    字母组合
    Java基础知识强化之集合框架笔记09:Collection集合迭代器使用的问题探讨
    Java基础知识强化之集合框架笔记08:Collection集合自定义对象并遍历案例(使用迭代器)
    Java基础知识强化之集合框架笔记07:Collection集合的遍历之迭代器遍历
  • 原文地址:https://www.cnblogs.com/kenwoo/p/9309264.html
Copyright © 2020-2023  润新知