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