13

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

 3 years ago
source link: http://www.cnblogs.com/artech/p/inside-pipeline-05.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们知道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);
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK