通過重建Hosting系統理解HTTP請求在ASP.NET Core管道中的處理流程[中]:管道如何處理請求

来源:http://www.cnblogs.com/artech/archive/2016/10/12/rebuild-pipeline-02.html
-Advertisement-
Play Games

ASP.NET Core請求處理管道由一個伺服器和一組中間件構成。如果想非常深刻地認識ASP.NET Core的請求處理管道,我覺得可以分兩個步驟來進行:首先,我們可以在忽略具體細節的前提下搞清楚管道處理HTTP請求的總體流程;在對總體流程有了大致瞭解之後,我們再來補充這些刻意忽略的細節。為了讓讀者... ...


從上面的內容我們知道ASP.NET Core請求處理管道由一個伺服器和一組中間件構成,所以從總體設計來講是非常簡單的。但是就具體的實現來說,由於其中涉及很多對象的交互,很少人能夠地把它弄清楚。如果想非常深刻地認識ASP.NET Core的請求處理管道,我覺得可以分兩個步驟來進行:首先,我們可以在忽略具體細節的前提下搞清楚管道處理HTTP請求的總體流程;在對總體流程有了大致瞭解之後,我們再來補充這些刻意忽略的細節。為了讓讀者朋友們能夠更加容易地理解管道處理HTTP請求的總體流程,我們根據真實管道的實現原理再造了一個“迷你版的管道”。[本文已經同步到《ASP.NET Core框架揭秘》之中] [源代碼從這裡下載]

目錄
一、建立在“模擬管道”上的應用
二、HttpApplication——一組中間件的有序集合
三、HttpContext——對當前HTTP上下文的抽象
四、伺服器——實現對請求的監聽、接收和響應

一、建立在“模擬管道”上的應用

再造的迷你管道不僅僅體現了真實管道中處理HTTP請求的流程,並且對於其中涉及的介面和類型,我們也基本上採用了相同的命名方式。但是為了避免“細枝末節”造成的干擾,我會進行最大限度的裁剪。對於大部分方法,我們只會保留最核心的邏輯。對於一些介面,我們會剔除那些與核心流程無關的成員。在通過這個模擬管道講解HTTP請求的總體處理流程之前,我們先來看看如何在它基礎上開發一個簡單的應用。

我們在這個模擬管道上開發一個簡單的應用來發佈圖片。具體的應用場景是這樣:我們將圖片文件保存在伺服器上的某個目錄下,客戶端可以通過發送HTTP請求併在請求地址上指定文件名的方式來獲取目標圖片。如下圖所示,我們利用瀏覽器向針對某張圖片的地址(“http://localhost:3721/images/hello.png”)發送請求後,獲取到的目標圖片(hello.png)會直接顯示到瀏覽器上。除此之外,如果指定的圖片地址沒有包含擴展名(“.png”),我們的也會幫助我們自動匹配一個文件名(不包含擴展名)相同的圖片。

4

由於我們模擬的管道採用與真實管道一致的應用編程介面,所以兩種採用的編程模式也是一致的。這個用於發佈圖片的應用是通過如下幾行簡單的代碼構建起來的。如下麵的代碼片斷所示,我們在Main方法中創建了一個WebHostBuilder對象,在調用其Build方法創建應用宿主的WebHost之前,我們調用擴展方法UseHttpListener註冊了一個類型為HttpListenerServer的伺服器。這個HttpListenerServer是我們自己定義的伺服器,它利用一個HttpListener對象實現了針對HTTP請求的監聽、接收和最終的響應。監聽地址(“http://localhost:3721/images”)是通過調用擴展方法UseUrls指定的。

   1: public class Program
   2: {
   3:     public static void Main()
   4:     {
   5:         new WebHostBuilder()
   6:             .UseHttpListener()
   7:             .UseUrls("http://localhost:3721/images")
   8:             .Configure(app => app.UseImages(@"c:\images"))
   9:             .Build()
  10:             .Start();
  11:  
  12:         Console.Read();
  13:     }
  14: }

應用針對圖片獲取請求的處理是通過我們自定義的中間件完成的。在調用WebHostBuilder的Configure方法定義管道過程中,我們調用IApplicationBuilder介面的擴展方法UseImages完成了針對這個中間件的定製。在調用這個擴展方法的時候,我們指定了存放圖片的目錄(“c:\images”),我們通過瀏覽器獲取的這個圖片(“hello.png”)就保存在這個目錄下。

二、HttpApplication——一組中間件的有序集合

ASP.NET Core請求處理管道由一個伺服器和一組有序排列的中間件組合而成。我們可以在這基礎上作進一步個抽象,將後者抽象成一個HttpApplication對象,那麼該管道就成了一個Server和HttpApplication的綜合體(如下圖所示)。Server會將接收到的HTTP請求轉發給HttpApplication對象,後者會針對當前請求創建一個上下文,併在此上下文中處理請求,請求處理完成並完成響應之後HttpApplication會對此上下文實施回收釋放處理。

5

我們通過具有如下定義的IHttpApplication<TContext>類型來表示上述的這個HttpApplication,泛型參數TContext代表它針對每個請求而建立的上下文。一個HttpApplication對象在接收到Server轉發的請求之後需要完成三項基本的操作,即創建上下文在上下文中處理請求以及請求處理完成之後釋放上下文,這三個基本操作正好通過對應的三個方法來完成。

   1: public interface IHttpApplication<TContext>
   2: {
   3:     TContext CreateContext(IFeatureCollection contextFeatures); 
   4:     Task ProcessRequestAsync(TContext context);
   5:     void DisposeContext(TContext context, Exception exception);
   6: }

用於創建上下文的CreateContext方法具有一個類型為IFeatureCollection介面的參數。顧名思義,這個介面用於描述某個對象所具有的一組特性,我們可以將它視為一個Dictionary<Type, object>對象,字典對象的Value代表特性對象,Key則表示該對象的註冊類型(可以是特性描述對象的真實類型、真實類型的基類或者實現的介面)。我們可以調用Get方法根據指定的註冊類型得到設置的特性對象,特性對象的註冊則通過Set方法來完成。我們自定義的FeatureCollection類型採用最簡單的方式實現了這個介面。

   1: public interface IFeatureCollection
   2: {
   3:     TFeature Get<T>();
   4:     void Set<T>(T instance);
   5: }
   6:  
   7: public class FeatureCollection : IFeatureCollection
   8: {
   9:     private ConcurrentDictionary<Type, object> features = new ConcurrentDictionary<Type, object>();
  10:  
  11:     public TFeature Get<T>()
  12:     {
  13:         object feature;
  14:         return features.TryGetValue(typeof(T), out feature) 
  15:             ? (T)feature 
  16:             : default(T);
  17:     }
  18:  
  19:     public void Set<T>(T instance)
  20:     {
  21:         features[typeof(T)] = instance;
  22:     }
  23: }

管道採用的HttpApplication是一個類型為 HostingApplication的對象。如下麵的代碼片段所示,這個類型實現了介面IHttpApplication<Context>,泛型參數Context是一個針對當前請求的上下文對象。一個Context對象是對一個HttpContext的封裝,後者是真正描述當前HTTP請求的上下文,承載著最為核心的上下文信息。除此之外,我們還為Context定義了Scope和StartTimestamp兩個屬性,兩者與日誌記錄和事件追蹤有關,前者被用來將針對同一請求的多次日誌記錄關聯到同一個上下文範圍(即Logger的BeginScope方法的返回值);後者表示開始處理請求的時間戳,如果在完成請求處理的時候記錄下當前的時間戳,我們就可以計算出整個請求處理所花費的時間。

   1: public class HostingApplication : IHttpApplication<Context>
   2: {
   3:     //省略成員定義
   4: }
   5:  
   6: public class Context
   7: {
   8:     public HttpContext     HttpContext { get; set; }
   9:     public IDisposable     Scope { get; set; }
  10:     public long            StartTimestamp { get; set; }
  11: }

下圖所示的UML體現了與HttpApplication相關的核心介面/類型之間的關係。總得來說,通過泛型介面IHttpApplication<TContext>表示HttpApplication是對註冊的中間件的封裝。HttpApplication在一個自行創建的上下文中完成對伺服器接收請求的處理,而上下文根據表述原始HTTP上下文的特性集合來創建,這個特性集合通過介面IFeatureCollection來表示,FeatureCollection是該介面的預設實現者。ASP.NET Core 預設使用的HttpApplication是一個HostingApplication對象,它創建的上下文是一個Context對象,一個Context對象是對一個HttpContext和其他與日誌相關上下文信息的封裝。

6

三、HttpContext——對當前HTTP上下文的抽象

用來描述當前HTTP請求的上下文的HttpContext對於ASP .NET Core請求處理管道來說是一個非常重要的對象,我們不僅僅可以利用它獲取當前請求的所有細節,還可以直接利用它完成對請求的響應。HttpContext是一個抽象類,很多用於描述當前HTTP請求的上下文信息的屬性被定義在這個類型中。在這個這個模擬管道模型中,我們僅僅保留瞭如下兩個核心的屬性,即表示請求和響應的Requst和Response屬性。

   1: public abstract class HttpContext
   2: {
   3:     public abstract HttpRequest     Request { get; }
   4:     public abstract HttpResponse    Response { get; }
   5: }

表示請求和響應的HttpRequest和HttpResponse同樣是抽象類。簡單起見,我們僅僅保留少數幾個與演示實例相關的屬性成員。如下麵的代碼片段所示,我們僅僅為HttpRequest保留了表示當前請求地址的Url屬性和表示基地址的PathBase屬性。對於HttpResponse來說,我們保留了三個分別表示輸出流(OutputStream)、媒體類型(ContentType)和響應狀態碼(StatusCode)的屬性。

   1: public abstract class HttpRequest
   2: {
   3:     public abstract Uri    Url { get; }
   4:     public abstract string PathBase { get; }
   5: }
   6:  
   7: public abstract class HttpResponse
   8: {
   9:     public abstract Stream     OutputStream { get; }
  10:     public abstract string     ContentType { get; set; }
  11:     public abstract int        StatusCode { get; set; }
  12: }

ASP.NET Core預設使用的HttpContext是一個類型為DefaultHttpContext對象,在介紹DefaultContext的實現原理之前,我們必須瞭解這樣一個事實:對應這個管道來說,請求的接收者和最終響應者都是伺服器,伺服器接收到請求之後會創建自己的上下文來描述當前請求,針對請求的響應也通過這個原始上下文來完成。以我應用中註冊的HttpListenerServer為例,由於它內部使用的是一個類型為HttpListener的監聽器,所以它總是會創建一個HttpListenerContext對象來描述接收到的請求,針對請求的響應也是利用這個HttpListenerContext對象來完成的。

但是對於建立在管道上的應用來說,它們是不需要關註管道究竟採用了何種類型的伺服器,更不會關註由這個伺服器創建的這個原始上下文。實際上我們的應用不僅統一使用這個DefaultHttpContext對象來獲取請求信息,同時還利用它來完成對請求的響應。很顯然,應用這使用的這個DefaultHttpContext對象必然與伺服器創建的原始上下文存在某個關聯,這種關聯是通過上面我們提到過的這個FeatureCollection對象來實現的。

image

如上圖所示,不同類型的伺服器在接收到請求的時候會創建一個原始的上下文,接下來它會將針對原始上下文的操作封裝成一系列標準的特性對象(特性類型實現統一的介面)。這些特性對象最終伺服器被組裝成一個FeatureCollection對象,應用程式中使用的DefaultHttpContext就是根據它創建出來的。當我們調用DefaultHttpContext相應的屬性和方法時,在它的內部實際上藉助封裝的特性對象去操作原始的上下文。

一旦瞭解DefaultHttpContext是如何操作原始HTTP上下文之後,對於DefaultHttpContext的定義就很好理解了。如下麵的代碼片斷所示,DefaultHttpContext具有一個IFeatureCollection類型的屬性HttpContextFeatures,它表示的正是由伺服器創建的用於封裝原始HTTP上下文相關特性的FeatureCollection對象。通過構造函數的定義我們知道對於一個DefaultHttpContext對象來說,表示請求和響應的分別是一個DefaultHttpRequest和DefaultHttpResponse對象。

   1: public class DefaultHttpContext : HttpContext
   2: { 
   3:     public IFeatureCollection HttpContextFeatures { get;}
   4:  
   5:     public DefaultHttpContext(IFeatureCollection httpContextFeatures)
   6:     {
   7:         this.HttpContextFeatures = httpContextFeatures;
   8:         this.Request      = new DefaultHttpRequest(this);
   9:         this.Response     = new DefaultHttpResponse(this);
  10:     }
  11:     public override HttpRequest      Request { get; }
  12:     public override HttpResponse     Response { get; }
  13: }

由不同類型的伺服器創建的特性對象之所以能夠統一被DefaultHttpContext所用,原因在於它們的類型都實現統一的介面,在模擬的管道模型中,我們定義瞭如下兩個針對請求和響應的特性介面IHttpRequestFeature和IHttpResponseFeature,它們與HttpRequest和HttpResponse具有類似的成員定義。

   1: public interface IHttpRequestFeature
   2: {
   3:     Uri    Url { get; }
   4:     string PathBase { get; }
   5: }
   6:  
	   

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

-Advertisement-
Play Games
更多相關文章
  • 編寫MFC程式時,想列印出調試信息,使用cout後,發現程式並沒有像想象中那樣自動彈出命令行視窗,要輸出的信息也沒地方去查看。百度後知道要手動調出命令行視窗,才可以看到輸出的信息。 百度上介紹了兩種方法,一種是通過添加代碼,在程式中建立命令行視窗的對象。這裡介紹一種比較簡單的方法。 右鍵解決方案,打 ...
  • 1、視圖中 2、控制器的action中 3、過濾器中 比如在ActionFilterAttribute中,這個時候一般是自己實現一個繼承類,然後重寫相關的方法。 在重寫的方法中如果需要控制器的名稱。 4、公共方法中 ...
  • (本文是從我的舊博客遷移過來的) 問題地址:http://acm.timus.ru/problem.aspx?space=1&num=1258 前幾日在博客園看到這種線上測試的時候,有一種相見恨晚的感覺,於是隨便選了一道感興趣的題(No.1258:Pool)開始做。為了準確瞭解題的意思,我把題翻譯成 ...
  • 本次將要很大家分享的是一個跨平臺運行的服務插件 - TaskCore.MainForm,此框架是使用.netcore來寫的,現在netcore已經支持很多系統平臺運行了,所以將以前的Task.MainForm改良成跨平臺的服務共大家使用和相互交流;本來這篇應該分享的是nginx+iis+redis+ ...
  • ...
  • 剖析 AssemblyInfo.cs - 從這裡瞭解常用的特性 Attribute 【博主】反骨仔 【原文】http://www.cnblogs.com/liqingwen/p/5944391.html 序 上次,我們通過《C# 知識回顧 - 特性 Attribute》已經瞭解如何創建和使用特性 A ...
  • 配置 ASP.NET HTTP 運行時設置,以確定如何處理對 ASP.NET 應用程式的請求,配置節及其描述如下所示。 <httpRuntime executionTimeout="110" 指定在被 ASP.NET 自動關閉前,允許執行請求的最大秒數 maxRequestLength="4096" ...
  • 從eclipse到android studio的安卓開發經驗告訴我原聲開發才是硬道理,其實以前很抵觸html5開發app的,雖然沒有去瞭解過,但是冥冥中就覺得它運行速度太慢了,載入渲染根本比不上原生開發,並且如果系統與硬體交互比較深的話就更沒法使用html5了。一個偶然機會,我開始接觸html5開發 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...