ASP.NET Core管道详解[5]: ASP.NET Core应用是如何启动的?[上篇]

我们知道ASP.NET Core应用的请求处理管道是由一个IServer对象和IHttpApplication对象构成的。我们可以根据需要注册不同类型的服务器,但在默认情况下,IHttpApplication是一个HostingApplication对象。一个HostingApplication对象由指定的RequestDelegate对象来完成所有的请求处理工作,而后者代表所有中间件按照注册的顺序串联而成的委托链。所有的这一切都被GenericWebHostService整合在一起,在对这个承载Web应用的服务做进一步介绍之前,下面先介绍与它相关的配置选项。[本文节选自《ASP.NET Core 3框架揭秘》第13章, 更多关于ASP.NET Core的文章请点这里]

目录
一、配置选项:GenericWebHostServiceOptions
二、承载服务:GenericWebHostService
三、应用启动流程
四、关闭应用

一、配置选项:GenericWebHostServiceOptions

GenericWebHostService这个承载服务的配置选项类型为GenericWebHostServiceOptions。如下面的代码片段所示,这个内部类型有3个属性,其核心配置选项由WebHostOptions属性承载。GenericWebHostServiceOptions类型的ConfigureApplication属性返回的Action<IApplicationBuilder>对象用来注册中间件,启动过程中针对中间件的注册最终都会转移到这个属性上。

internal class GenericWebHostServiceOptions
{
    public WebHostOptions WebHostOptions { get; set; }
    public Action<IApplicationBuilder> ConfigureApplication { get; set; }
    public AggregateException HostingStartupExceptions { get; set; }
}

如何放置你的初始化代码》提出,可以利用一个外部程序集中定义的IHostingStartup实现类型来完成初始化任务,而GenericWebHostServiceOptions类型的HostingStartupExceptions属性返回的AggregateException对象就是对这些初始化任务执行过程中抛出异常的封装。一个WebHostOptions对象承载了与IWebHost相关的配置选项,虽然在基于IHost/IHostBuilder的承载系统中,IWebHost接口作为宿主的作用已经不存在,但是WebHostOptions这个配置选项依然被保留下来。

public class WebHostOptions
{
    public string ApplicationName { get; set; }
    public string Environment { get; set; }
    public string ContentRootPath { get; set; }
    public string WebRoot { get; set; }
    public string StartupAssembly { get; set; }
    public bool PreventHostingStartup { get; set; }
    public IReadOnlyList<string> HostingStartupAssemblies { get; set; }
    public IReadOnlyList<string> HostingStartupExcludeAssemblies { get; set; }
    public bool CaptureStartupErrors { get; set; }
    public bool DetailedErrors { get; set; }
    public TimeSpan ShutdownTimeout { get; set; }

    public WebHostOptions() => ShutdownTimeout = TimeSpan.FromSeconds(5.0);
    public WebHostOptions(IConfiguration configuration);
    public WebHostOptions(IConfiguration configuration, string applicationNameFallback);
}

一个WebHostOptions对象可以根据一个IConfiguration对象来创建,当我们调用这个构造函数时,它会根据预定义的配置键从该IConfiguration对象中提取相应的值来初始化对应的属性。

public static class WebHostDefaults
{
    public static readonly string ApplicationKey = "applicationName";
    public static readonly string StartupAssemblyKey = "startupAssembly";
    public static readonly string DetailedErrorsKey = "detailedErrors";
    public static readonly string EnvironmentKey = "environment";
    public static readonly string WebRootKey = "webroot";
    public static readonly string CaptureStartupErrorsKey = "captureStartupErrors";
    public static readonly string ServerUrlsKey = "urls";
    public static readonly string ContentRootKey = "contentRoot";
    public static readonly string PreferHostingUrlsKey = "preferHostingUrls";
    public static readonly string PreventHostingStartupKey = "preventHostingStartup";
    public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds";

    public static readonly string HostingStartupAssembliesKey= "hostingStartupAssemblies";
    public static readonly string HostingStartupExcludeAssembliesKey= "hostingStartupExcludeAssemblies";
}

二、承载服务:GenericWebHostService

从如下所示的代码片段可以看出,GenericWebHostService的构造函数中会注入一系列的依赖服务或者对象,其中包括用来提供配置选项的IOptions<GenericWebHostServiceOptions>对象、作为管道“龙头”的服务器、用来创建ILogger对象的ILoggerFactory对象、用来发送相应诊断事件的DiagnosticListener对象、用来创建HttpContext上下文的IHttpContextFactory对象、用来创建IApplicationBuilder对象的IApplicationBuilderFactory对象、注册的所有IStartupFilter对象、承载当前应用配置的IConfiguration对象和代表当前承载环境的IWebHostEnvironment对象。在GenericWebHostService构造函数中注入的对象或者由它们创建的对象(如由ILoggerFactory对象创建的ILogger对象)最终会存储在对应的属性上。

internal class GenericWebHostService : IHostedService
{
    public GenericWebHostServiceOptions Options { get; }
    public IServer Server { get; }
    public ILogger Logger { get; }
    public ILogger LifetimeLogger { get; }
    public DiagnosticListener DiagnosticListener { get; }
    public IHttpContextFactory HttpContextFactory { get; }
    public IApplicationBuilderFactory ApplicationBuilderFactory { get; }
    public IEnumerable<IStartupFilter> StartupFilters { get; }
    public IConfiguration Configuration { get; }
    public IWebHostEnvironment HostingEnvironment { get; }

    public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options,
        IServer server, ILoggerFactory loggerFactory,
        DiagnosticListener diagnosticListener, IHttpContextFactory httpContextFactory,
        IApplicationBuilderFactory applicationBuilderFactory,
        IEnumerable<IStartupFilter> startupFilters, IConfiguration configuration,
        IWebHostEnvironment hostingEnvironment);

    public Task StartAsync(CancellationToken cancellationToken);
    public Task StopAsync(CancellationToken cancellationToken);
}

三、应用启动流程

由于ASP.NET Core应用是由GenericWebHostService服务承载的,所以启动应用程序本质上就是启动这个承载服务。承载GenericWebHostService在启动过程中的处理流程基本上体现在如下所示的StartAsync方法中,该方法中刻意省略了一些细枝末节的实现,如输入验证、异常处理、诊断日志事件的发送等。

internal class GenericWebHostService : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        //1. 设置监听地址
        var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
        var addresses = serverAddressesFeature?.Addresses;
        if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
        {
            var urls = Configuration[WebHostDefaults.ServerUrlsKey];
            if (!string.IsNullOrEmpty(urls))
            {
                serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(Configuration, WebHostDefaults.PreferHostingUrlsKey);

                foreach (var value in urls.Split(new[] { ';' },StringSplitOptions.RemoveEmptyEntries))
                {
                    addresses.Add(value);
                }
            }
        }

        //2. 构建中间件管道
        var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);
        Action<IApplicationBuilder> configure = Options.ConfigureApplication;
        foreach (var filter in StartupFilters.Reverse())
        {
            configure = filter.Configure(configure);
        }
        configure(builder);
        var handler = builder.Build();

        //3. 创建HostingApplication对象
        var application = new HostingApplication(handler, Logger, DiagnosticListener, HttpContextFactory);

        //4. 启动服务器
        return Server.StartAsync(application, cancellationToken);
    }
}

我们将实现在GenericWebHostService类型的StartAsync方法中用来启动应用程序的流程划分为如下4个步骤。

  • 设置监听地址:服务器的监听地址是通过IServerAddressesFeature接口表示的特性来承载的,所以需要将配置提供的监听地址列表和相关的PreferHostingUrls选项(表示是否优先使用承载系统提供地址)转移到该特性中。
  • 构建中间件管道:通过调用IWebHostBuilder对象和注册的Startup类型的Configure方法针对中间件的注册会转换成一个Action<IApplicationBuilder>对象,并复制给配置选项GenericWebHostServiceOptions的ConfigureApplication属性。GenericWebHostService承载服务会利用注册的IApplicationBuilderFactory工厂创建出对应的IApplicationBuilder对象,并将该对象作为参数调用这个Action<IApplicationBuilder>对象就能将注册的中间件转移到IApplicationBuilder对象上。但在此之前,注册IStartupFilter对象的Configure方法会优先被调用,IStartupFilter对象针对前置中间件的注册就体现在这里。代表注册中间件管道的RequestDelegate对象最终通过调用IApplicationBuilder对象的Build方法返回。
  • 创建HostingApplication对象:在得到代表中间件管道的RequestDelegate之后,GenericWebHostService对象进一步利用它创建出HostingApplication对象,该对象对于服务器来说就是用来处理由它接收请求的应用程序。
  • 启动服务器:将创建出的HostingApplication对象作为参数调用作为服务器的IServer对象的StartAsync方法后,服务器随之被启动。此后,服务器绑定到指定的地址监听抵达的请求,并为接收的请求创建出对应的HttpContext上下文,后续中间件将在这个上下文中完成各自对请求的处理任务。请求处理结束之后,生成的响应最终通过服务器回复给客户端。

四、关闭应用

关闭GenericWebHostService服务之后,只需要按照如下方式关闭服务器即可。除此之外,StopAsync方法还会利用EventSource的形式发送相应的事件,我们在前面针对诊断日志的演示可以体验此功能。

internal class GenericWebHostService : IHostedService
{
    public async Task StopAsync(CancellationToken cancellationToken) => Server.StopAsync(cancellationToken);
}

请求处理管道[1]: 模拟管道实现
请求处理管道[2]: HttpContext本质论
请求处理管道[3]: Pipeline = IServer +  IHttpApplication<TContext
请求处理管道[4]: 中间件委托链
请求处理管道[5]: 应用承载[上篇
请求处理管道[6]: 应用承载[下篇]

posted @ 2020-12-02 09:17  Artech  阅读(1903)  评论(0编辑  收藏  举报