學習ASP.NET Core,怎能不瞭解請求處理管道[1]: 中間件究竟是個什麼東西?

来源:http://www.cnblogs.com/artech/archive/2016/11/14/asp-net-core-real-pipeline-01.html
-Advertisement-
Play Games

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>的委托對象呢?原因很簡單,中間件並不孤立地存在,所有註冊的中間件最終會根據註冊的先後順序組成一個鏈表,每個中間件不僅僅需要完成各自的請求處理任務外,還需要驅動鏈表中的下一個中間件。

1

如上圖所示,對於一個由多個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對象就是這麼一個特性集合。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 環境搭建: 1)node.js版本>5.0,NPM版本>3.0,TypeScript版本>2.0(全裝最新版就好了) 2)安裝NTVS 1.2(node tools for vs),TSVS dev 1.4(TS for VS) 3)構建package.json,tsconfig.json,gulp ...
  • 1、環境部署: windows server 2008R2環境 2、相關軟體 SVN(源代碼管理器:jenkins通過插件從源代碼管理器下載代碼) Jenkins(主角)地址:http://ftp.yz.yamagata-u.ac.jp/pub/misc/jenkins/windows-stable ...
  • ReSharper 10.0.0.1 Ultimate 完美破解補丁使用方法,本資源來自互聯網,感謝吾樂吧軟體站的分享。 ReSharper是一款由jetbrains開發的針對C#, VB.NET, ASP.NET, XML, 和 XAML的編輯器。沿襲了jetbrains開發工具一貫的優良傳統,R ...
  • 添加引用時生成”勾選允許生成非同步操作” Wcf非同步調用三種方式: 第一種:直接調用非同步方法 var serviceClient = new MyServiceClient(); serviceClient.MessageAsync(); erviceClient.Close(); 第二種:Begin ...
  • 一、MessageBox的Buttons MessageBox.Show可以出現有按鈕的對話框 例如: DialogResult dr = MessageBox.Show("是否要繼續嗎?", "警告!!!", MessageBoxButtons.OKCancel);//它彈出的對話框如下圖所示if ...
  • 一、客戶端設計思路 1.理順設計思路,架構框架 2.設計界面 3.編寫後臺代碼 4.資料庫訪問 二、公共控制項 1、Button(按鈕): ⑴ Enabled :確定是否啟用控制項 ⑵ Visible:確定控制項是否課件; 2、CheckBox(多選項) 、CheckListBox -(多選項列表) 3、 ...
  • Asp.Net MVC4 BundleConfig文件合併、壓縮,網站優化加速 Asp.Net MVC4 BundleConfig文件合併、壓縮,網站優化加速 瀏覽器在向伺服器發送請求的時候,請求的文件鏈接數量是有限制的,如果頁面文件少就沒有什麼問題了,如果文件太多就會導致鏈接失敗等等問題。針對這個 ...
  • C# 知識回顧 - 事件入門 【博主】反骨仔 【原文】http://www.cnblogs.com/liqingwen/p/6057301.html 序 之前通過《C# 知識回顧 - 委托 delegate》、《C# 知識回顧 - 委托 delegate (續)》介紹了委托的基本知識,這次我們來看看 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...