在《管道是如何處理HTTP請求的?》中,我們對ASP.NET Core的請求處理管道的構成以及它對請求的處理流程進行了詳細介紹,接下來我們需要了解的是這樣一個管道是如何被構建起來的。這樣一個管道由一個服務器和一個HttpApplication構成,前者負責監聽請求并將接收的請求傳遞給給HttpAppkication對象處理,后者則將請求處理任務委托給注冊的中間件來完成。中間件的注冊是通過ApplicationBuilder對象來完成的,所以我們先來了解一下這究竟是個怎樣的對象。
目錄
ApplicationBuilder
StartupLoader
WebHost
WeHostBuilder
總結
一、ApplicationBuilder
我們所說的ApplicationBuilder是對所有實現了IApplicationBuilder接口的所有類型及其對象的統稱。注冊到WebHostBuilder上的啟動類型具有一個用于管道定值的Configure方法,它利用作為參數的ApplicationBuilder對象進行中間件的注冊。由于ApplicationBuilder與組成管道的中間件具有直接的關系,所以我們得先來說說中間件在管道中究竟體現為一個怎樣的對象。
中間件在請求處理流程中體現為一個類型為Func<RequestDelegate,RequestDelegate>的委托對象,對于很多剛剛接觸請求處理管道的讀者朋友們來說,可能一開始對此有點難以理解,所以容來略作解釋。我們上面已經提到過RequestDelegate這么一個委托,它相當于一個Func<HttpContext, Task>對象,該委托對象表示針對提供的HttpContext所做進行一項處理操作,這項操作代表某個中間件針對請求的處理。那為何我們不直接用一個RequestDelegate對象來表示一個中間件,而將它表示成一個Func<RequestDelegate,RequestDelegate>對象呢?
在大部分應用中,我們會針對具體的請求處理需求注冊多個不同的中間件,這些中間件按照注冊時間的先后順序進行排列進而構成我們所謂的請求處理管道。對于某個中間件來說,在它完成了自身的請求處理任務之后,需要將請求傳遞給下一個中間件作后續的處理。Func<RequestDelegate,RequestDelegate>中作為輸入參數的RequestDelegate對象代表一個委托鏈,體現了后續中間件對請求的處理,當前中間件將自身實現的請求處理任務添加到這個委托鏈中,而返回RequestDelegate對象代表最新的委托鏈。
以右圖所示的管道為例,如果用一個Func<RequestDelegate,RequestDelegate>來表示中間件B,那么作為輸入參數的RequestDelegate對象代表的是C對請求的處理操作,而返回值則代表B和C先后對請求處的處理操作。如果一個Func<RequestDelegate,RequestDelegate>代表第一個從服務器接收請求的中間件(比如A),那么執行該委托對象返回的RequestDelegate實際上體現了整個管道對請求的處理。
在對中間件有了充分的了解之后,我們來看看用于注冊中間件的IApplicationBuilder接口的定義。如下所示的是經過裁剪后的IApplicationBuilder接口的定義,我們只保留了兩個核心的方法,其中Use方法實現了針對中間件的注冊,另一個Build方法則將所有注冊的中間件轉換成一個RequestDelegate對象。
1: public interface IApplicationBuilder
2: {
3: RequestDelegate Build();
4: IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
5: }
從編程便利性考慮,很多預定義的中間件都具有用于注冊的擴展方法,比如我們調用擴展方法UseStaticFiles來注冊處理靜態文件請求的中間件。對于我們演示的發布圖片的應用來說,它也是通過調用一個具有如下定義的擴展方法UseImages來注冊處理圖片請求的中間件。
1: public static class ApplicationBuilderExtensions
2: {
3: public static IApplicationBuilder UseImages(this IApplicationBuilder app, string directory)
4: {
5: Func<RequestDelegate, RequestDelegate> middleware = next =>
6: {
7: return context =>
8: {
9: string fileName = context.Request.Url.LocalPath.TrimStart('/');
10: if (string.IsNullOrEmpty(Path.GetExtension(fileName)))
11: {
12: fileName += ".jpg";
13: }
14: fileName = Path.Combine(directory, fileName);
15: context.Response.WriteFile(fileName, "image/jpg");
16: return next(context);
17: };
18: };
19: return app.Use(middleware);
20: }
21: }
ASP.NET Core默認使用的是一個類型為ApplicationBuilder的對象來注冊中間件,我們采用如下的代碼片斷來模擬它的實現邏輯。我們采用一個List<Func<RequestDelegate, RequestDelegate>>對象來存放所有注冊的中間件,并調用Aggregate方法將它轉換成一個RequestDelegate對象。
1: public class ApplicationBuilder : IApplicationBuilder
2: {
3: private IList<Func<RequestDelegate, RequestDelegate>> middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
4:
5: public RequestDelegate Build()
6: {
7: RequestDelegate seed = context => Task.Run(() => {});
8: return middlewares.Reverse().Aggregate(seed, (next, current) => current(next));
9: }
10:
11: public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
12: {
13: middlewares.Add(middleware);
14: return this;
15: }
16: }
ASP.NET Core并不會直接創建ApplicationBuilder對象來注冊中間件,而是利用對應的工廠來創建它。創建愛你ApplicationBuilder的工廠通過接口IApplicationBuilderFactory表示,在模擬的管道中我們將這個接口簡化成如下的形式,該接口的默認實現者ApplicationBuilderFactory會直接創建一個ApplicationBuilder類型的對象。
1: public interface IApplicationBuilderFactory
2: {
3: IApplicationBuilder CreateBuilder();
4: }
5:
6: public class ApplicationBuilderFactory : IApplicationBuilderFactory
7: {
8: public IApplicationBuilder CreateBuilder()
9: {
10: return new ApplicationBuilder();
11: }
12: }
二、StartupLoader
一個服務器和一組中間件組成了ASP .NET Core的HTTP請求處理管道,中間件的注冊通過調用ApplicationBuilder的Use方法來完成,而這一切實現在注冊為啟動類型的Configure方法中,我們可以將針對這個方法的調用抽象成一個類型為Action <IApplicationBuilder> 的委托對象。在管道初始化過程中,WebHost必須獲取并執行這個委托以完成中間件的注冊工作。具體來說這個委托對象的獲取是利用一個名為StatupLoader對象來完成的。
這里的StartupLoader是對所有實現了IStartupLoader接口的所有類型機器對象的統稱,我們在模擬管道中將這個接口作了如下所示的簡化。IStartupLoader接口具有的唯一方法GetConfigureDelegate根據指定的啟動類型生成一個Action <IApplicationBuilder> 。對于默認實現該接口的StartupLoader類來說,它的GetConfigureDelegate方法返回的委托會以反射的方式執行定義在指定啟動類型的Configure方法。簡單起見,我們假設這個Configure方法為實例方法,啟動對象可以直接調用默認無參構造函數來創建。
1: public interface IStartupLoader
2: {
3: Action<IApplicationBuilder> GetConfigureDelegate(Type startupType);
4: }
5:
6: public class StartupLoader : IStartupLoader
7: {
8: public Action<IApplicationBuilder> GetConfigureDelegate(Type startupType)
9: => app => startupType.GetMethod("Configure").Invoke(Activator.CreateInstance(startupType), new object[] { app });
10: }
三、WebHost
ASP.NET Core的請求處理管道是由作為應用宿主的WebHost對象創建出來的,后者是對所有實現了IWebHost接口的所有類型及其對象的統稱,我們在模擬管道中將這個接口作了如下的簡化,僅僅保留了唯一的方法Start。隨著WebHost因Start方法的調用而被開啟,整個管道也隨之被建立起來。
1: public interface IWebHost
2: {
3: void Start();
4: }
通過上面的介紹我們知道請求處理管道可以理解為一個服務器和一個HttpApplication的組合,當我們創建出一個服務器并指定一個具體的HttpApplication對象調用其Start方法將其啟動時,這個管道就被建立起來。服務器的創建是利用ServerFactory來完成的,而默認采用的HttpApplication類型為HostingApplication。
當我們創建一個HostingApplication對象的時候,需要指定一個類型為RequestDelegate的委托對象,后者通過調用ApplicationBuilder的Build方法獲得,代表了所有注冊的中間件針對當前請求的處理。所以HostingApplication的創建需要一個ApplicationBuilder對象,這個對象通過ApplicationBuilderFactory來創建。在調用ApplicationBuilder的Build方法將注冊的中間件轉換成RequestDelegate委托之前,需要完成針對中間件的注冊工作。實現在啟動類型的Configure方法中針對中間件的注冊可以體現為一個Action <IApplicationBuilder>對象,這對委托對象可以通過StartupLoader來獲取。
綜上所述,為了創建并啟動一個服務器,WebHost至少需要一個ServerFactory和ApplicationBuilderFactory來創建服務器和ApplicationBuilder,還需要一個StartupLoader來最終完成對中間件的注冊。除此之外,還需要知道注冊到WebHostBuilder上的啟動類型。由于依賴注入被廣泛應用到了ASP.NET Core的請求處理管道中,對于前面三個對象,會先以服務的形式注冊到DI容器中,那么WebHost只需要利用ServiceProvider對象根據對應的服務接口得到這三個對象。
1: public class WebHost : IWebHost
2: {
3: private IServiceProvider serviceProvider;
4: private Type startupType;
5:
6: public WebHost(IServiceCollection appServices, Type startupType)
7: {
8: this.serviceProvider = appServices.BuildServiceProvider();
9: this.startupType = startupType;
10: }
11:
12: public void Start()
13: {
14: IApplicationBuilder applicationBuilder = serviceProvider.GetRequiredService<IApplicationBuilderFactory>().CreateBuilder();
15: serviceProvider.GetRequiredService<IStartupLoader>().GetConfigureDelegate(startupType)(applicationBuilder);
16: IServer server = serviceProvider.GetRequiredService<IServerFactory>().CreateServer();
17: server.Start(new HostingApplication(applicationBuilder.Build()));
18: }
19: }
由上面代碼片段提供的這個極簡版的WebHost類通過構造函數的參數提供包含原始服務注冊的ServiceCollection對象和啟動類型,我們利用前者創建對應的ServiceProvider。在Start方法中,我們利用ServiceProvider得到一個ApplicationBuilder對象和一個StartupLoader對象。我們將啟動類型作為參數調用StartupLoader的GetConfigureDelegate方法得到一個Action<IApplicationBuilder>對象。接下來,我們將ApplicationBuilder對象作為參數調用這個Action<IApplicationBuilder>委托對象,后者會執行定義在啟動類型中的Configure方法并最終完整對中間件的注冊。
在這之后,我們利用ServiceProvider得到一個ServiceFactory對象并利用它創建出代碼服務器的Server對象。為了調用其Start方法,我們需要創建一個HostingApplication對象作為參數,而后者的創建需要一個代表所有中間件針對當前請求進行處理的RequestDelegate對象,這個對象直接通過調用ApplicationBuilder對象的Build方法得到。當服務器因Start方法的調用而被啟動后,整個請求處理管道被正式建立起來。
四、WebHostBuilder
作為應用宿主的WebHost創建了ASP.NET Core的請求處理管道,而WebHost又是由它的工廠WebHostBuilder創建的。WebHostBuilder是對所有實現了IWebHostBuilder接口的所有類型及其對象的統稱,我們在模擬管道中對這個接口做了極大的簡化,僅僅保留了如下面代碼片段所示的三個方法成員。針對WebHost的創建通過Build方法實現,額外兩個方法(UseStartup和UseServer)分別用于注冊啟動類型和用于創建服務器的ServerFactory。
1: public interface IWebHostBuilder
2: {
3: IWebHostBuilder UseStartup(Type startupType);
4: IWebHostBuilder UseServer(IServerFactory factory);
5: IWebHost Build();
6: }
依賴注入在ASP.NET Core 請求處理管道中得到了極大的應用,創建WebHost提供的ServiceCollection對象最初由WebHostBuilder提供。WebHost在構建管道時使用的一系列服務對象(ApplicationBuilderFactory和StartupLoader)最初都由WebHostBuilder注冊到這個ServiceCollection對象中,這一切都體現如下所示的這個默認使用的WebHostBuilder類型中。
1: public class WebHostBuilder : IWebHostBuilder
2: {
3: private Type startupType;
4: private IServiceCollection services;
5:
6: public WebHostBuilder()
7: {
8: services = new ServiceCollection()
9: .AddTransient<IStartupLoader, StartupLoader>()
10: .AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
11: }
12:
13: public IWebHost Build() => new WebHost(services, this.startupType);
14:
15: public IWebHostBuilder UseServer(IServerFactory factory)
16: {
17: services.AddSingleton<IServerFactory>(factory);
18: return this;
19: }
20:
21: public IWebHostBuilder UseStartup(Type startupType)
22: {
23: this.startupType = startupType;
24: return this;
25: }
26: }
五、總結
綜上所述,我們已經對ASP.NET Core應用如何利用WebHostBuilder最終構建出請求處理管道的流程以及管道自身處理請求的流程具有了一定的了解,現在我們來做一個簡單的總結。請求處理管道涉及到四個核心的對象,它們分別是WebHostBuilder、WebHost、Server和HttpApplication,它們之間具有如圖11所示的關系。我們通過WebHostBuilder來創建WebHost,并領用后者來構建請求處理管道。
請求處理管道通過一個Server和一個HttpApplication對象組成,后者是對所有注冊的中間件的封裝。當WebHost被啟動的時候,它會創建Server和HttpApplication對象,并將后者作為參數調用Server的Start方法以啟動服務器。啟動后的Server開啟監聽請求并利用HttpApplication來處理接收到請求。當HttpApplication完成了所有請求處理工作之后,它會利用Server完成對請求的最終響應。
上面所述的所有內容都是針對我們自定義的模擬管道來介紹的,雖然我們對這個模擬管道做了極大的簡化,但是它依然體現了ASP.NET Core管道處理請求的真實流程,而且真實管道的創建方式也與模擬管道基本一致。如果讀者朋友們能夠對這個模擬管道具有深刻的理解,我相信對真實管道的把握就會變得非常容易。
一、采用管道處理HTTP請求
二、創建一個“迷你版”的管道來模擬真實管道請求處理流程
三、管道如何處理HTTP請求的
四、管道是如何被創建出來的
文章列表