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