ASP.NET Core管道雖然在結構組成上顯得非常簡單,但是在具體實現上卻涉及到太多的對象,所以我們在 “通過重建Hosting系統理解HTTP請求在ASP.NET Core管道中的處理流程”(上篇、中篇、下篇) 中圍繞著一個經過極度簡化的模擬管道講述了真實管道構建的方式以及處理HTTP請求的流程... ...
ASP.NET Core管道雖然在結構組成上顯得非常簡單,但是在具體實現上卻涉及到太多的對象,所以我們在 “通過重建Hosting系統理解HTTP請求在ASP.NET Core管道中的處理流程”(上篇、中篇、下篇) 中圍繞著一個經過極度簡化的模擬管道講述了真實管道構建的方式以及處理HTTP請求的流程。在本系列 中,我們會還原構建模擬管道時可以捨棄和改寫的部分,向讀者朋友們呈現一個真是的HTTP請求處理管道。 ASP.NET Core 的請求處理管道由一個伺服器與一組有序排列的中間件構成,前者僅僅完成請求監聽、接收和響應這些與底層網路相關的工作,至於請求接收之後和響應之前的所有工作都交給中間件來完成。ASP.NET Core的中間件通過一個類型Func<RequestDelegate, RequestDelegate>的委托對象來表示,而RequestDelegate也是一個委托,它代表一項請求處理任務。 [本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、RequestDelegate
二、HttpContext
FeatureCollection
DefaultHttpContext
HttpContextFactory
三、ApplicationBuilder
ApplicationBuilderFactory
中間件類型
中間件類型的註冊
一、RequestDelegate
伺服器接受到抵達的HTTP請求之後會構建一個描述當前請求的原始上下文,伺服器的類型決定了這個原始上下文的類型,比如在我們模擬管道預設採用的HttpListenerServer由於採用HttpListener來監聽、接收並響應請求,所以它對應的原始上下文是一個HttpListenerContext對象。但是對於管道的後續部分,即由註冊的中間件構建的鏈表,它們需要採用統一的方式來處理請求,所以伺服器最終會根據原始的上下文來創建一個抽象的HTTP上下文,後者通過抽象類HttpContext來表示。
我們不僅可以利用這個HttpContext獲取描述當前請求的上下文信息,同樣可以利用它來實現對響應的控制。針對當前請求的任何處理操作總是在這麼一個上下文中進行,所以一項請求處理任務完全可以抽象成一個類型Func<HttpContext,Task>的委托來表示,實際上具有如下定義的RequestDelegate委托具有類似的定義。
1: public delegate Task RequestDelegate(HttpContext context);
每個中間件都承載著獨立的請求處理任務,它本質上也體現了在當前HttpContext下針對請求的處理操作,那麼為什麼中間件不直接通過一個RequestDelegate對象來表示,而是表示為一個類型為Func<RequestDelegate, RequestDelegate>的委托對象呢?原因很簡單,中間件並不孤立地存在,所有註冊的中間件最終會根據註冊的先後順序組成一個鏈表,每個中間件不僅僅需要完成各自的請求處理任務外,還需要驅動鏈表中的下一個中間件。
如上圖所示,對於一個由多個Func<RequestDelegate, RequestDelegate>對象組成的中間鏈表來說,某個中間件會將後一個Func<RequestDelegate, RequestDelegate>對象的返回值作為輸入,而自身的返回值則作為前一個中間件的輸入。某個中間件執行之後返回的RequestDelegate對象不僅僅體現了自身對請求的處理操作,而是體現了包含自己和後續中間件一次對請求的處理。那麼對於第一個中間件來說,它執行後返回的RequestDelegate對象實際上體現了整個應用對請求的處理邏輯。
二、 HttpContext
對當前上下文的抽象解除了管道對具體伺服器類型的依賴, 這使我們可以為ASP.NET Core應用自由地選擇承載(Hosting)方式,而不是像傳統的ASP.NET應用一樣只能寄宿在IIS之中。抽象HTTP上下文的目的是為了實現對請求處理流程的抽象,只有這樣我們才能將針對請求的某項操作體現在一個標準的中間件上,有了這個這個標準化的中間件才有所謂的請求處理管道。
ASP.NET Core通過具有如下所示的HttpContext類來表示這麼一個抽象的HTTP上下文。對於一個HttpContext對象來說,它的核心體現在用於描述請求和響應的Request和Response屬性之上。除此之外,我們還可以通過它獲取與當前請求相關的其他上下文信息,比如用來控制用戶認證的AuthenticationManager對象和代表當前請求用戶的ClaimsPrincipal對象,以及描述當前HTTP連接的ConnectionInfo對象和用於控制WebSocket的WebSocketManager。我們可以獲取並控制當前會話,也可以獲取或者設置調試追蹤的ID。
1: public abstract class HttpContext
2: {
3:
4: public abstract HttpRequest Request { get; }
5: public abstract HttpResponse Response { get; }
6:
7: public abstract AuthenticationManager Authentication { get; }
8: public abstract ClaimsPrincipal User { get; set; }
9: public abstract ConnectionInfo Connection { get; }
10: public abstract WebSocketManager WebSockets { get; }
11: public abstract ISession Session { get; set; }
12: public abstract string TraceIdentifier { get; set; }
13: public abstract CancellationToken RequestAborted { get; set; }
14: public abstract IDictionary<object, object> Items { get; set; }
15:
16: public abstract IServiceProvider RequestServices { get; set; }
17: public abstract IFeatureCollection Features { get; }
18: }
當需要中指對請求的處理時,我們可以通過為RequestAborted屬性設置一個CancellationToken對象從而將終止通知發送給管道。如果需要對整個管道共用一些與當前上下文相關的數據,我們可以將它保存在通過Items屬性表示的字典中。我們一再提到依賴註入被廣泛地應用ASP.NET Core管道中,HttpContext的RequestServices屬性返回的根據在應用啟動時註冊的服務而創建的ServiceProvider。只要相應的服務被預先註冊到指定的服務介面上,我們就可能利用這個ServiceProvider根據這個介面得到對應的服務對象。
1: public abstract class HttpRequest
2: {
3: public abstract HttpContext HttpContext { get; }
4: public abstract string Method { get; set; }
5: public abstract string Scheme { get; set; }
6: public abstract bool IsHttps { get; set; }
7: public abstract HostString Host { get; set; }
8: public abstract PathString PathBase { get; set; }
9: public abstract PathString Path { get; set; }
10: public abstract QueryString QueryString { get; set; }
11: public abstract IQueryCollection Query { get; set; }
12: public abstract string Protocol { get; set; }
13: public abstract IHeaderDictionary Headers { get; } >
14: public abstract IRequestCookieCollection Cookies { get; set; }
15: public abstract string ContentType { get; set; }
16: public abstract Stream Body { get; set; }
17: public abstract bool HasFormContentType { get; }
18: public abstract IFormCollection Form { get; set; }
19:
20: public abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken);
21: }
如上所示的是抽象類HttpRequest是對HTTP請求的描述,它是HttpContext的只讀屬性Request的返回類型。我們可以利用這個對象獲取到描述當前請求的各種相關信息,比如請求的協議(HTTP或者HTTPS)、HTTP方法、地址,也可以獲取代表請求的HTTP消息的首部和主體。
在瞭解了表示請求的抽象類HttpRequest之後,我們再來認識一個與之相對的用於描述響應HttpResponse類型。如下麵的代碼片斷所示,HttpResponse依然是一個抽象類,我們可以通過定義在它之上的屬性和方法來控制對請求的響應。從原則上講,我們對請求的所做的任意類型的響應都可以利用它來說實現。當我們通過表示當前上下文的HttpContext對象得到表示響應的HttpResponse之後,我們不僅僅可以將希望的內容寫入響應消息的主體,還可以設置響應狀態碼以及添加相應的首部。
1: public abstract class HttpResponse
2: {
3: public abstract HttpContext HttpContext { get; }
4: public abstract int StatusCode { get; set; }
5: public abstract IHeaderDictionary Headers { get; }
6: public abstract Stream Body { get; set; }
7: public abstract long? ContentLength { get; set; }
8: public abstract IResponseCookies Cookies { get; }
9: public abstract bool HasStarted { get; }
10:
11: public abstract void OnStarting(Func<object, Task> callback, object state);
12: public virtual void OnStarting(Func<Task> callback);
13: public abstract void OnCompleted(Func<object, Task> callback, object state);
14: public virtual void RegisterForDispose(IDisposable disposable);
15: public virtual void OnCompleted(Func<Task> callback);
16: public virtual void Redirect(string location);
17: public abstract void Redirect(string location, bool permanent);
18: }
FeatureCollection
HttpContext的另一個只讀屬性Features返回一組“特性”對象。在ASP.NET Core管道式處理設計中,特性是一個非常重要的概念,特性是實現抽象化HttpContext的途徑。具體來說,伺服器在接收到請求之後會創建一個由自身類型決定的原始的上下文,管道不僅僅利用這個原始上下文來獲取與請求相關的信息,它對請求的最終響應實際上也是通過這個原始上下文來完成的。所以對一個HttpContext對象來說,由它描述的上下文信息不僅僅來源於這個原始的上下文,我們針對HttpContext所做的任何響應操作最終都需要分發給這個原始上下文來完成, 否則是不會生效的。抽象的HttpContext和原始上下文之間的“雙向綁定”究竟是如何實現的呢?
這個所謂的“雙向綁定”即使其實很簡單。當原始上下文被創建出來之後,伺服器會將它封裝成一系列標準的特性對象,HttpContext正是對這些特性對象的封裝。一般來說,這些特性對象所對應的類型均實現了某個預定義的標準介面,介面中不僅僅定義相應的屬性來讀寫原始上下文中描述的信息,還定義了相應的方法來操作原始上下文。HttpContext的屬性Features返回的就是這組特性對象的集合,它的返回類型為IFeatureCollection,我們將實現了該介面的類型以及對應的對象統稱為FeatureCollection。
1: public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>
2: {
3: TFeature Get<TFeature>();
4: void Set<TFeature>(TFeature instance);
5:
6: bool IsReadOnly { get; }
7: object this[Type key] { get; set; }
8: int Revision { get; }
9: }
一個FeatureCollection對象本質上就是一個Key和Value分別為Type和Object類型的欄位,話句話說,特性對象通過對應的介面類型註冊到HttpContext之上。我們通過調用Set方法將一個特性對象針對指定的類型(一般為特性介面)註冊到這個字典對象上,並通過Get方法根據註冊的類型獲取它。特性對象的註冊和獲取也可以利用定義的索引來完成。如果IsReadOnly屬性返回True,我們將不能註冊新的特性或者修改已經註冊的特性。 整數類型的之都屬性Revision可以視為整個FeatureCollection對象的版本,不論是採用何種方式註冊新的特性還是修改現有的特性,這個屬性的值都將改變。
具有如下定義的FeatureCollection類實現了IFeatureCollection介面,我們預設使用的FeatureCollection就是這麼一個類型的對象。FeatureCollection具有兩個構造函數重載,預設無參構造函數幫助我們創建一個空的特性集合,另一個構造函數則需要指定一個FeatureCollection對象來提供預設特性。對於採用第二個構造函數重載創建的 FeatureCollection對象來說,當我們通過指定某個特性介面類型試圖獲取對應的特性對象時,如果對應的特性沒有註冊到當前FeatureCollection對象上,而是註冊到提供預設特性的FeatureCollection對象上,後者將會提供最終的特性。
1: public class FeatureCollection : IFeatureCollection
2: {
3: //其他成員
4: public FeatureCollection();
5: public FeatureCollection(IFeatureCollection defaults);
6: }
對於FeatureCollection類型來說,它 的IsReadOnly總是返回False,所以它永遠是可讀可寫的。對於調用預設無參構造函數創建的FeatureCollection對象來說,它 的Revision預設返回零。如果我們通過指定另一個FeatureCollection對象為參數調用第二個構造函數來創建一個FeatureCollection對象,前者的Revision屬性值將成為後者同名屬性的預設值。不論我們採用何種形式(調用Set方法或者索引)添加一個新的特性或者改變了一個已經註冊的特性,FeatureCollection對象的Revision屬性都將自動遞增。上述的這些關於FeatureCollection的特性都體現在如下所示的代碼片段中。
1: FeatureCollection defaults = new FeatureCollection();
2: Debug.Assert(defaults.Revision == 0);
3:
4: defaults.Set<IFoo>(new Foo());
5: Debug.Assert(defaults.Revision == 1);
6:
7: defaults[typeof(IBar)] = new Bar();
8: Debug.Assert(defaults.Revision == 2);
9:
10: FeatureCollection features = new FeatureCollection(defaults);
11: Debug.Assert(features.Revision == 2);
12: Debug.Assert(features.Get<IFoo>().GetType() == typeof(Foo));
13:
14: features.Set<IBaz>(new Baz());
15: Debug.Assert(features.Revision == 3);
DefaultHttpContext
ASP.NET Core預設使用的HttpContext類型為DefaultHttpContext,上面我們介紹的針對描述原始上下文“特性集合”來創建HttpContext的策略就體現在這個類型之上。DefaultHttpContext具有一個如下的構造函數,作為參數的FeatureCollection對象就是這麼一個特性集合。