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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM