1. 程式人生 > >ASP.NET Core管道詳解[5]: ASP.NET Core應用是如何啟動的?[上篇]

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]: 應用承載