ASP.Net請求處理機制初步探索之旅(2)核心

發表於2015-03-18

開篇:上一篇我們瞭解了一個請求從客戶端發出到服務端接收並轉到ASP.Net處理入口的過程,這篇我們開始探索ASP.Net的核心處理部分,藉助強大的反編譯工具,我們會看到幾個熟悉又陌生的名詞(類):HttpRuntime、HttpWorkerRequest、HttpContext、HttpApplication等。

一、第一個入口:ISAPIRuntme.ProcessRequest()

  ISAPIRuntime是進入NET託管環境的入口,它在方法中通過一個ecb控制程式碼指向了當前請求報文體的記憶體地址,將HTTP請求報文簡單封裝為一個HttpWorkerRequest物件,然後就是各種我們經常聽到的PR(ProcessRequest)方法了。

①呼叫ISAPIRuntime物件的ProcessRequest方法進入ASP.NET處理流程

  通過Reflector,我們可以看到在ISAPIRuntime中的這個入口方法:ProcessRequest

②首先根據ecb控制程式碼建立HttpWorkerRequest物件封裝原始請求報文

關於HttpWorkerRequest:

在Asp.Net中準備用於處理的請求,都必須封裝為HttpWorkerRequest型別的物件,HttpWorkerRequest是一個抽象型別。這裡建立的是一個ISAPIWorkerRequest型別,它繼承於HttpWorkerRequest類。

轉到ISAPIWorkerRequest類的CreateWorkerRequest方法中,看到首先判斷當前IIS伺服器的版本(IIS6 or IIS7?),然後建立適合不同IIS的具體WorkerRequest物件,預設都是InProc程式內的,當然,也有OutOfProc程式外的。

由於HttpWorkerRequest類封裝的請求報文很原始,很複雜,所以微軟沒有將其公開出來。

二、第二個入口:HttpRuntime.ProcessRequest()

HttpRuntime是ASP.NET請求處理的第二個入口。當請求進來,首先進入HttpRuntime,由HttpRuntime來決定如何處理請求。預設情況下,在machine.config和Web.config中並沒有顯式定義httpRuntime節點,但該節點是有預設值的,如下:

通常情況下,我們可以在Web.config中更改httpRuntime節點的預設值,如下:

①其次執行HttpRuntime的ProcessRequestNoDemand方法封裝HttpContext物件

在HttpRuntime類中,有一個稱為ProcessRequestNoDemand的靜態方法。這裡建立並初始化HttpWorkerRequest物件後,呼叫了HttpRuntime的這個ProcessRequestNoDemand方法。於是,我們轉到ProcessRequestNoDemand方法,可以看到如下程式碼:

該方法先從請求佇列中取出一個請求,然後更新請求的引用計數器的資訊,然後再將HttpWorkerRequest物件傳入ProcessRequestNow方法來處理請求。

這裡我們還可以看到_theRuntime這個欄位,它是HttpRuntime類的一個靜態欄位,在HttpRuntime的靜態建構函式中進行初始化。

再回到ProcessRequestNoDemand方法中,我們看到最後是由ProcessRequestNow方法來接棒。於是,我們轉到ProcessRequestNow方法再來看看:

我們看到在這個方法中又呼叫了_theRuntime的例項方法:ProcessRequestInternal,還是把HttpWorkerRequest物件作為引數傳遞了過去。於是,我們再轉到ProcessRequestInternal這個方法中,發現了一個重要的物件:HttpContext物件。

HttpContext的建構函式中,根據HttpWorkerRequest物件建立了HttpContext物件,這是一個重要的Http上下文物件,兩個重要型別的欄位也隨之被初始化:HttpRequest物件和HttpResponse物件。

相信大家在進行ASP.NET開發時,經常使用這兩個型別的例項。例如,我們可以通過HttpContext.Current獲取到這個例項,且該例項會在整個生命週期中存活,我們通過它可以獲取到一些常用物件,如Request,Response,Session 等。

②通過HttpApplicationFactory得到一個具體的HttpApplication例項

讓我們再次回到HttpRuntimeProcessRequestInternal這個方法中,剛剛HttpContext物件被建立後,緊接著又幹了什麼事?讓我們看看原始碼:

首先,我們看到了一個非常熟悉的字眼:IHttpHandler,我們的ashx、aspx不都是實現了這個IHttpHandler介面的嗎?於是,我們來看看這句:IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context); 它是通過一個叫做HttpApplicationFactory的工廠類來獲取了一個HttpApplication的例項,並將HttpContext上下文物件作為引數傳遞了進去。於是,懷著好奇心,我們轉到這個方法內部去看看:

原來,它是呼叫了一個例項方法:GetNormalApplicationInstance來獲取HttpApplication。於是,我們再轉到這個方法的內部去看看:

通過檢視這段程式碼,它首先維護著一個HttpApplication池(_freeList,本質上就是一個Stack棧),然後判斷可用的HttpApplication例項的數量(_numFreeAppInstances)是否大於0?如果存在可用的,則從池中出棧,然後將可用數量減1。最後,再判斷可用的數量是否小於最低限制的數量,如果小於那麼則將最低限制的數量設定為目前可用的數量。

那麼,如果目前HttpApplication池暫時沒有可用的例項呢?我們看到了這一句:state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType); 通過此段程式碼,新建了一個新的HttpApplication例項,通過繼續深入檢視,原來是通過反射的方式將Global檔案所編譯後的類封裝出來一個HttpApplication例項。

補充之一:_theApplicationType是_theApplicationFactory.EnsureInited();中被賦值的

補充之二:全域性事件中例如Application_Start方法如何保證只執行一次?在_theApplicationFactory.EnsureAppStartCalled(context);方法中,判斷_appOnStartCalled標誌,如果是false則呼叫FireApplicationOnStart方法觸發Application_Start方法,然後更改_appOnStartCalled標誌。

三、第三個入口:HttpApplication.Init()

在前兩個入口中,HttpApplication例項被建立,現在HttpApplication需要進行初始化請求處理管道,來分別處理ASP.Net WebForm或ASP.Net MVC等型別的頁面的響應操作。

①初始化HttpModules

  讓我們再次回到HttpRuntime的ProcessRequestInternal這個方法中,剛剛HttpApplication例項被建立後,開始了一系列的初始化操作,如下圖所示,呼叫了其InitInternal方法進行了初始化。

轉到InitInternal方法內部,發現呼叫了一個非常重要的方法:InitModules()。

InitModules這個方法中,首先通過讀取Web.config配置檔案中關於HttpModule的資訊,然後將其傳遞給HttpModule的集合,如下程式碼所示:

然後,呼叫InitModulesCommon方法,遍歷上面這個_moduleCollection集合,分別對其每一個HttpModule執行其對應的Init方法。

註冊19個請求處理管道事件

接上述操作之後,InitInternal方法內部還執行了這樣一句:this._stepManager.BuildSteps(this._resumeStepsWaitCallback); 它完成了19個請求處理管道事件的註冊工作。

從上面的程式碼可知,ApplicationStepManager物件的BuildSteps方法被呼叫,完成HttpApplication 19個管道事件的註冊。這個方法很重要,它將建立各種HttpApplication.IExecutionStep儲存到一個陣列列表 _execSteps 中:

這樣做的目的在於:便於在後面的BeginProcessRequest方法內部呼叫ResumeSteps方法依次執行這些物件的Execute()方法,完成各個事件的執行。

③開始依次處理請求處理管道中的各個事件

讓我們再返回到HttpRuntime中的ProcessRequestInternal方法中,HttpApplication例項已建立好,HttpModules已初始化,請求處理管道中的19個事件也已經註冊好,現在需要的只是一一呼叫HttpModule中各個事件對應的執行方法即可。

在上述程式碼中,通過執行BeginProcessRequest方法,觸發了ResumeSteps方法依次執行每個請求處理管道事件,也就進入了我們所說的“請求處理管道”中。

關於請求處理管道:

HttpApplication 採用處理管道的方法進行處理,將處理的過程分成多個步驟,每個步驟通過事件的形式暴露給程式設計師,這些事件按照固定的處理順序依次觸發,程式設計師通過編寫事件處理方法就可以自定義每一個請求的擴充套件處理過程。

對於 HttpApplication 來說,到 ASP.NET 4.0 版本,提供了19 個標準事件,如下圖所示:

至於在請求處理管道中的細節,我們在Part 3中再看,今天就到此為止,謝謝!

四、核心過程總覽

①ISAPIRuntime->HttpWorkerRequest->HttpRuntime

②HttpRuntime->HttpContext->HttpApplication

③到目前為止的總體流程概覽

  • 首先,我們從自己的瀏覽器通過網路訪問Web伺服器
  • 當ASP.NET接收到第一個請求時,將會建立一個應用程式域,然後會建立一個宿主環境
  • 然後ASP.NET建立並初始化核心物件HttpContext、HttpRequest和HttpResponse
  • 然後建立HttpApplication物件的例項來啟動應用程式
  • 通過進入請求處理管道來處理具體的請求

參考資料

(1)Darren Ji,《ASP.NET MVC請求處理管道宣告週期的19個關鍵環節》:http://www.cnblogs.com/darrenji/p/3795661.html

(2)木宛城主,《ASP.NET那點不為人知的事兒》:http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html

(3)Tony He,《ASP.NET請求處理機制》:http://www.cnblogs.com/cilence/archive/2012/05/28/2520712.html

(4)兩會的部落格,《IIS是怎樣處理ASP.NET請求的》:http://www.cnblogs.com/hkncd/archive/2012/03/23/2413917.html

(5)wjn2000,《ASP.NET請求處理過程(IIS6)》:http://www.cnblogs.com/wjn2010/archive/2011/04/21/2024341.html

(6)農村出來的大學生,《ASP.NET網頁請求處理全過程(反編譯)》:http://www.cnblogs.com/poorpan/archive/2011/09/25/2190308.html

相關文章