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

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

從上面的內容我們知道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:  
   7: public interface IHttpResponseFeature
   8: {
   9:     Stream     OutputStream { get; }
  10:     string     ContentType { get; set; }
            
           

相關推薦

通過重建Hosting系統理解HTTP請求ASP.NET Core管道處理流程[上]採用管道處理請求

之所以稱ASP.NET Core是一個Web開發平臺,而不是一個單純的開發框架,源於它具有一個極具擴充套件性的請求處理管道,我們可以通過對這個管道的定製來滿足各種場景下的HTTP處理需求。ASP. NET Core應用的很多特性,比如路由、認證、會話、快取等,都是通過對管道的定製來實現的。我們甚至可以通過管道

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

從上面的內容我們知道ASP.NET Core請求處理管道由一個伺服器和一組中介軟體構成,所以從總體設計來講是非常簡單的。但是就具體的實現來說,由於其中涉及很多物件的互動,很少人能夠地把它弄清楚。如果想非常深刻地認識ASP.NET Core的請求處理管道,我覺得可以分兩個步驟來進行:首先,我們可以在忽略具體細節

通過重建Hosting系統理解HTTP請求ASP.NET Core管道處理流程[下]管道是如何構建起來的?

在《中篇》中,我們對管道的構成以及它對請求的處理流程進行了詳細介紹,接下來我們需要了解的是這樣一個管道是如何被構建起來的。總的來說,管道由一個伺服器和一個HttpApplication構成,前者負責監聽請求並將接收的請求傳遞給給HttpApplication物件處理,後者則將請求處理任務委託給註冊的中介軟體來

ASP.NET Core應用基本程式設計模式[1]管道式的請求處理

HTTP協議自身的特性決定了任何一個Web應用的工作模式都是監聽、接收並處理HTTP請求,並且最終對請求予以響應。HTTP請求處理是管道式設計典型的應用場景:可以根據具體的需求構建一個管道,接收的HTTP請求像水一樣流入這個管道,組成這個管道的各個環節依次對其做相應的處理。雖然ASP.NET Core的請求處

跨域請求asp.net core webapi 介面,返回自定義header

這個簡單的問題對於初學core的我來說還是折騰了好久,然後加了一個群問了一下,終於解決了,感謝大神的指點; 官方api: 總結:閱讀官方的api文件很重要啊,慚愧啊; 然後以此備忘吧。 我在header裡面返回自定義引數count,startup.cs配置如下:

Windows下docker的安裝,將ASP.NET Core程序部署在docker

很好 etc all 虛擬 mark work 記得 配置 netcore 參考文章: https://www.cnblogs.com/jRoger/p/aspnet-core-deploy-to-docker.html https://www.cnblogs.com/jR

Asp.Net Core 2.0實現HttpResponse繁切換

== sed 存儲 中文簡體 選擇 .net tin contains nts 隨筆背景:因為項目中有個簡單的功能是需要實現中文簡體到繁體的切換,數據庫中存儲的源數據都是中文簡體的,為了省事就想著通過HttpHeader的方式來控制Api返回對應的繁體數據。 實現方式:通過

Asp.net Core 2.1 Kestrel 現在支援 多協議處理(Tcp)

地址:https://github.com/davidfowl/MultiProtocolAspNetCore.git 在一個Kestrel服務上可以同時處理Tcp,Http,Https等多種協議。 通過實現 ConnectionHandler 處理接入連線,ConnectionContext.Trans

ASP.NET Core MVC 授權的擴展自定義 Authorize Attribute 和 IApplicationModelProvide

and rabl resource 而是 async expire des nat 使用 一、概述 ASP.NET Core MVC 提供了基於角色( Role )、聲明( Chaim ) 和策略 ( Policy ) 等的授權方式。在實際應用中,可能采用部門(

ASP.NET Core 2.0和Angular 4從頭開始構建用於車輛管理的Web應用程式

  目錄 介紹 背景 使用程式碼 I)伺服器端 a)先決條件 b)設定專案 c)設定資料庫 d)使用AutoMapper e)使用Swagger f)執行API II)客戶端 a)先決條件 b)設定專案 c)實現服務 d)

ASP.NET core webapi 換埠 _平臺windows (2)

ASP.NET core webapi 換埠 _平臺:windows (2) 首先看我們專案的根目錄: 發現我們有個檔案 launchSettings.json 在這個json檔案裡面有我們的設定 我們進入頁面就發現我們的專案地址: 原本我們啟動是5000

ASP.NET core webapi建立案例 _平臺windows (1)

ASP.NET core webapi建立案例 _平臺:windows (1) 案例連結:https://download.csdn.net/download/qq_36051316/10856209 編譯環境:.net core 2.2.101 核心命令: dotnet

ASP.NET Core MVC 直接執行報錯物件不支援“addEventListener”屬性或方法

場景:第一次建立了ASP.NET Core MVC專案,我們知道,什麼都不動,就可以執行的,出來的是Core2.0的頁面,類似於.NetFramework建立MVC一樣,就在此時,如果你的預設的調式瀏覽

ASP.NET Core Web API 整合測試使用 Bearer Token

在 ASP.NET Core Web API 整合測試一文中, 我介紹了ASP.NET Core Web API的整合測試.  在那裡我使用了測試專用的Startup類, 裡面的配置和開發時有一些區別, 例如裡面去掉了使用者身份驗證相關的中介軟體. 但是有些被測試的行為裡面需要用到身份/授權資訊. 所以

如何在ASP.NET Core自定義中介軟體讀取Request.Body和Response.Body的內容?

文章名稱: 如何在ASP.NET Core自定義中介軟體讀取Request.Body和Response.Body的內容? 作者: Lamond Lu 地址: https://www.cnblogs.com/lwqlun/p/10954936.html 原始碼: https://github.com/lamo

[ASP.NET Core 3框架揭祕] 依賴注入控制反轉

ASP.NET Core框架建立在一些核心的基礎框架之上,這些基礎框架包括依賴注入、檔案系統、配置選項和診斷日誌等。這些框架不僅僅是支撐ASP.NET Core框架的基礎,我們在進行應用開發的時候同樣會頻繁地使用到它們。對於這裡提到的這幾個基礎框架,依賴注入尤為重要。ASP.NET Core應用在啟動以及後續

[ASP.NET Core 3框架揭祕] 依賴注入IoC模式

正如我們在《依賴注入:控制反轉》提到過的,很多人將IoC理解為一種“面向物件的設計模式”,實際上IoC不僅與面向物件沒有必然的聯絡,它自身甚至算不上是一種設計模式。一般來講,設計模式提供了一種解決某種具體問題的方案,但是IoC既沒有一個針對性的問題領域,其自身也沒有提供一種可操作性的解

[ASP.NET Core 3框架揭祕] 依賴注入依賴注入模式

IoC主要體現了這樣一種設計思想:通過將一組通用流程的控制權從應用轉移到框架之中以實現對流程的複用,並按照“好萊塢法則”實現應用程式的程式碼與框架之間的互動。我們可以採用若干設計模式以不同的方式實現IoC,比如我們在前面介紹的模板方法、工廠方法和抽象工廠,接下來我們介紹一種更有價值的I

[ASP.NET Core 3框架揭祕] 依賴注入一個Mini版的依賴注入框架

在前面的章節中,我們從純理論的角度對依賴注入進行了深入論述,我們接下來會對.NET Core依賴注入框架進行單獨介紹。為了讓讀者朋友能夠更好地理解.NET Core依賴注入框架的設計與實現,我們按照類似的原理建立了一個簡易版本的依賴注入框架,也就是我們在前面多次提及的Cat。原始碼下載普通服務的註冊與消費泛型

[ASP.NET Core 3框架揭祕] 配置[1]讀取配置資料[上篇]

提到“配置”二字,我想絕大部分.NET開發人員腦海中會立即浮現出兩個特殊檔案的身影,那就是我們再熟悉不過的app.config和web.config,多年以來我們已經習慣了將結構化的配置定義在這兩個XML格式的檔案之中。到了.NET Core的時代,很多我們習以為常的東西都發生了改變,其中就包括定義配置的方式