ASP.NET Web API的核心框架是一個消息處理管道,這個管道是一組HttpMessageHandler的有序組合。這是一個雙工管道,請求消息從一端流入並依次經過所有HttpMessageHandler的處理。在另一端,目標HttpController被激活,Action方法被執行,響應消息隨之被生成。響應消息逆向流入此管道,同樣會經過逐個HttpMessageHandler的處理。這是一個獨立於寄宿環境的抽象管道,如何實現對請求的監聽與接收,以及將接收的請求傳入消息處理管道進行處理並將管道生成的響應通過網絡回傳給客戶端,這就是Web API寄宿需要解決的問題。
目錄
一、HttpMessageHandler
二、DelegatingHandler
三、HttpServer
四、HttpRoutingDispatcher
五、HttpControllerDispatcher
一、HttpMessageHandler
ASP.NET Web API的消息處理管道由一組HttpMessageHandler經過“首尾相連”而成,ASP.NET Web API之所以具有較高的可擴展性,主要源於采用的管道式設計。雖然ASP.NET Web API框架旨在實現針對請求的處理和響應的回復,但是采用的處理策略因具體的場景而不同。
我們不可能也沒有必要創建一個“萬能”的處理器來滿足所有的請求處理需求,倒不如讓某個處理器只負責某個單一的消息處理功能。在具體的應用場景中,我們可以根據具體的消息處理需求來選擇所需的處理器並組成一個完整的消息處理管道。在這裡這個用於完成某個單一消息處理功能的處理器就是HttpMessageHandler。
這裡的“消息處理”具有兩個層面的含義,既包括針對請求消息的處理,還包括針對響應消息的處理。HttpMessageHandler直接或者間接繼承自具有如下定義的抽象類型HttpMessageHandler,該類型定義在命名空間“System.Net.Http”下。ASP.NET Web API通過類型HttpRequestMessage和HttpResponseMessage來表示管道處理的請求消息和響應消息,所以對HttpMessageHandler的定義就很好理解了。
1: public abstract class HttpMessageHandler : IDisposable
2: {
3: public void Dispose();
4: protected virtual void Dispose(bool disposing);
5: protected abstract Task
6: }
如上面的代碼片斷所示,抽象類HttpMessageHandler定義了一個受保護的抽象方法SendAsync,這是一個采用針對Task的“並行編程模式”的異步方法,在後續的章節中我們會看到ASP.NET Web API的應用程序接口基本上都采用這樣的定義方式。對於這個SendAsync方法來說,request參數表示傳遞給當前HttpMessageHandler進行處理的請求,這是一個HttpRequestMessage對象。另一個參數cancellationToken是一個用於發送取消操作信號的CancellationToken對象,如果讀者對.NET中的並行編程具有基本了解的話,相信對這個類型不會感到陌生。
針對請求消息和響應消息的處理均體現在這個SendAsync方法上。具體來說,針對請求消息的處理直接實現在SendAsync方法中,而針對響應消息的處理則通過其返回的Task
抽象類HttpMessageHandler實現了IDisposable接口,它按照“標准”的方式實現Dispose方法。如下面的代碼片斷所示,當我們調用Dispose方法的時候,HttpMessageHandler並沒有執行任何資源回收操作。當我們通過繼承這個抽象類自定義HttpMessagHandler的時候,可以將資源回收操作實現在重寫的Dispose方法中。
1: public abstract class HttpMessageHandler : IDisposable
2: {
3: //其他操作
4: public void Dispose()
5: {
6: this.Dispose(true);
7: GC.SuppressFinalize(this);
8: }
9:
10: protected virtual void Dispose(bool disposing)
11: {}
12: }
二、DelegatingHandler
我們說ASP.NET Web API消息處理管道是通過一組有序的HttpMessagHandler“首尾相連”而成,具體實現“管道串聯”是通過DelegatingHandler這個類型來完成的。顧名思義,DelegatingHandler具有委托功能,當它自己負責的消息處理任務完成之後可以委托另一個HttpMessagHandler進行後續的處理。如果這個被委托的也是一個DelegatingHandler對象,不就可以組成一個委托鏈了嗎?而這個委托鏈不就是由一個個DelegatingHandler組成的消息處理管道嗎?
如下面的代碼片斷所示,DelegatingHandler是一個繼承自HttpMessageHandler類的抽象類。上面我們所說的這個被委托的HttpMessagHandler由它的屬性InnerHandler表示。DelegatingHandler重寫了定義在其類的抽象方法SendAsync來調用InnerHandler屬性的同名方法。
1: public abstract class DelegatingHandler : HttpMessageHandler
2: {
3: protected internal override Task
4: public HttpMessageHandler InnerHandler get; set; }
5: }
正如上面所說,如果ASP.NET Web API的消息處理管道均由DelegatingHandler組成(位於管道尾端的HttpMessageHandler除外),我們就可以根據其InnerHandler獲得對被委托的HttpMessageHandler對象的引用,由此便構成具有如上圖所示的鏈式結構。組成ASP.NET Web API核心框架的消息處理管道就這麼簡單。
三、HttpServer
一般來說,對於構成ASP.NET Web API消息處理管道的所有HttpMessageHandler來說,除了處於尾端的那一個之外,其余的均為DelegatingHandler,那麼通過InnerHandler屬性維持著“下一個” HttpMessageHandler。作為這個HttpMessageHandler鏈“龍頭”的是一個HttpServer對象,該類型定義在命名空間“System.Web.Http”下。
如下面的代碼片斷所示,HttpServer直接繼承自DelegatingHandler。它具有兩個重要的只讀屬性(Configuration和Dispatcher),我們可以通過前者得到用於配置整個消息處理管道的HttpConfiguration對象,另外一個屬性Dispatcher返回的是處於整個消息處理管道“尾端”的HttpMessageHandler。
1: public class HttpServer : DelegatingHandler
2: {
3: public HttpConfiguration Configuration { get; }
4: public HttpMessageHandler Dispatcher { get; }
5:
6: public HttpServer();
7: public HttpServer(HttpMessageHandler dispatcher);
8: public HttpServer(HttpConfiguration configuration);
9: public HttpServer(HttpConfiguration configuration, HttpMessageHandler dispatcher);
10:
11: protected override void Dispose(bool disposing);
12: protected virtual void Initialize();
13: protected override Task
14: }
HttpServer的Configuration和Dispatcher屬性均可以在相應的構造函數中初始化。如果在構造HttpServer的時候沒有顯式指定這兩個屬性的值(調用默認的無參構造函數創建HttpServer),在默認情況下會創建一個HttpConfiguration作為Configuration的屬性值,而作為Dispatcher屬性值的則是一個HttpRoutingDispatcher對象,該類型定義在命名空間“System.Web.Http.Dispatcher”下。除此之外。由於HttpConfiguration類型實現了IDisposable接口,所以HttpServer重寫了虛方法Dispose並在該方法中完成了對HttpConfiguration對象的釋放。
當HttpServer對象被成功創建之後,對應的消息處理管道的“一頭一尾”已經確定下來。一頭指的就是HttpServer對象本身,一尾指的自然就是通過Dispatcher屬性引用的HttpMessageHandler對象了。ASP.NET Web API框架最大的擴展性就在於我們可以根據具體的消息處理需求來“定制”這個消息處理管道,它允許我們將自定義的HttpMessageHandler按照如左圖所示的方式“安裝”到這一頭一尾之間,但是這些處於“中間位置”的HttpMessageHandler是如何注冊呢?
既然整個管道都是由HttpConfiguration進行配置,那麼自定義HttpMessageHandler的注冊自然也可以利用它來完成。如下面的代碼片斷所示,HttpConfiguration具有一個只讀的集合類型的MessageHandlers,需要注冊的HttpMessageHandler需要添加到此集合之中。由於這是一個元素類型為DelegatingHandler的集合,所以我們自定義的HttpMessageHandler必須繼承自DelegatingHandler。
1: public class HttpConfiguration : IDisposable
2: {
3: //其他成員
4: public Collection
5: }
通過上面的給出的HttpServer類型定義我們可以看到它具有一個受保護的Initialize方法,該方法最終完成了對整個消息處理管道的構建。在重寫的SendAsync方法中,如果自身尚未被初始化,該Initialize方法會自動被調用以確保整個消息處理管道已經被成功構建。
四、HttpRoutingDispatcher
在默認情況下,作為消息處理管道“龍頭”的HttpServer的Dispatcher屬性返回一個HttpRoutingDispatcher對象,它可以視為這個消息處理管道的最後一個HttpMessageHandler。Web API調用請求一般都是針對定義在某個HttpController中的某個Action方法,所以消息處理管道最終需要激活相應的HttpController並執行對應的Action方法,HttpRoutingDispatcher完成了目標HttpController的激活和Action方法的執行。
如下面的代碼片斷所示,HttpRoutingDispatcher不再是DelegatingHandler的繼承者,它的直接基類是抽象類HttpMessageHandler。我們在構建一個HttpRoutingDispatcher對象的時候需要指定一個HttpConfiguration對象,而通過參數defaultHandler指定的HttpMessageHandler對於創建的HttpRoutingDispatcher對象來說具有重要的意義,因為HttpController的激活、Action方法的選擇與執行等後續操作實際上是由它來完成的。
1: public class HttpRoutingDispatcher : HttpMessageHandler
2: {
3: public HttpRoutingDispatcher(HttpConfiguration configuration);
4: public HttpRoutingDispatcher(HttpConfiguration configuration, HttpMessageHandler defaultHandler);
5:
6: protected override Task
7: }
雖然ASP.NET Web API消息處理管道不具有一個類似於HttpContext的對象來保存基於當前請求的上下文信息,但是表示請求消息的HttpRequestMessage對象具有一個通過Properties屬性表示的屬性字典,我們可以利用它來作為上下文數據的存放容器。
通過上面對HttpServer的介紹我們知道它會將當前SynchronizationContext和HttpConfiguration添加到表示當前請求的HttpRequestMessage對象的屬性字典中。與之類似,通過路由系統生成的HttpRouteData也以同樣的方式保存在HttpRequestMessage的屬性字典之中,我們可以直接調用HttpRequestMessage的如下兩個擴展方法GetRouteData和SetRouteData進行HttpRouteData的獲取和設置。
1: public static class HttpRequestMessageExtensions
2: {
3: //其他成員
4: public static IHttpRouteData GetRouteData(this HttpRequestMessage request);
5: public static void SetRouteData(this HttpRequestMessage request, IHttpRouteData routeData);
6: }
HttpRoutingDispatcher的SendAsync方法被執行時,它會判斷作為參數的HttpRequestMessage對象的屬性字典中是否具有這樣一個HttpRouteData對象。如果此HttpRouteData對象存在,它會直接將請求交付給創建時指定的HttpMessageHandler進行處理。這樣的情況會發生在Web Host寄宿模式下。
如果封裝路由數據的HttpRouteData對象尚未添加到表示被處理請求的HttpRequestMessage對象的屬性字典中,意味著針對請求的路由尚未發生,這種情況會發生在Self Host寄宿模式下。在這種情況下,HttpRoutingDispatcher會直接通過當前HttpConfiguration的Routes屬性得到全局路由表,並將HttpRequestMessage對象作為參數調用其GetRouteData方法以實現針對當前請求的路由解析。
如果執行路由表的GetRouteData方法返回一個具體的HttpRouteData對象,意味著當前請求與注冊的某個HttpRoute相匹配,HttpRoutingDispatcher會將這個HttpRouteData對象添加到HttpRequestMessage對象的屬性字典中。在這之後,ASP.NET Web API會將請求交付給創建時指定的HttpMessageHandler進行後續處理。如果執行GetRouteData方法返回Null,意味著當前請求與注冊的路由規則不匹配,客戶端會得到一個狀態為“404, Not Found”的響應。
五、HttpControllerDispatcher
我們從類型命名可以看出HttpRoutingDispatcher具有兩個基本的職能,即“路由(Routing)”和“消息分發(Dispatching)”。對於前者,它會調用當前路由表對請求消息實施路由解析進而生成用於封裝路由數據的HttpRouteData(如果這樣的HttpRouteData不存在於當前請求的屬性字典中)。對於後者,它會將請求直接分發給在創建時指定的HttpMessageHandler來完成進一步處理。
如果在構建HttpRoutingDispatcher對象的時候沒有通過參數defaultHandler顯式指定這麼一個HttpMessageHandler對象,默認情況下從它手中接管請求的HttpMessageHandler是一個具有如下定義的HttpControllerDispatcher的對象,該類型定義在命名空間“System.Web.Http.Dispatcher”下。HttpControllerDispatcher在整個消息處理管道中顯得尤為重要,因為目標HttpController的激活、Action方法的執行和響應生成均是由HttpControllerDispatcher來完成的。
1: public class HttpControllerDispatcher : HttpMessageHandler
2: {
3: public HttpControllerDispatcher(HttpConfiguration configuration);
4: protected override Task
5:
6: public HttpConfiguration Configuration { get; }
7: }
在我們引入HttpControllerDispatcher對象之後,ASP.NET Web API的消息處理管道將具有如右圖所示的結構。從這個結構來看,貌似HttpControllerDispatcher才是整個消息處理管道的最後一個HttpMessageHandler。這種說法沒有錯,但我個人還是傾向於將HttpControllerDispatcher視為“隸屬於” HttpRoutingDispatcher的“內部”HttpMessageHandler,所以仍將這個“包含” HttpControllerDispatcher的HttpRoutingDispatcher視為組成消息處理管道的最後一個HttpMessageHandler。
除此之外,“N個DelegagingHandler + 1個HttpMessageHander”這樣的鏈式結構也剛好與基於DelegagingHandler的委托鏈相匹配。對於讀者朋友來說,具體傾向於哪種說法並不重要,重要的是能夠深刻了解整個消息處理管道的是如何構成的。