8.2 ASP.NET 管線與應用程式生命週期
8.1 節介紹了IIS 的系統架構和HTTP 請求處理的整體流程,從中可以知道每個ASP.NET 網站都對應著一個Web 應用程序,此Web 應用程式可以回應HTTP 請求,為使用者提供所需的資訊。那麼, ASP.NET 應用程式具體是如何回應HTTP 請求的呢?包括哪些具體的處理流程?這涉及到ASP.NET 應用程式的生命週期問題。
8.2.1 ASP.NET應用程式生命週期*
本節以IIS 6 為例分步介紹ASP.NET 應用程式處理HTTP 請求的處理流程。 IIS 7 的處理過程與IIS 6 相比有些小變化,但大致上是一致的。
1 瀏覽器發出造訪某ASP.NET 網頁的HTTP 請求
假設這個請求是針對此網頁所屬的ASP.NET 應用程式的第一次請求。
當此請求到達Web 伺服器時,由HTTP.SYS 負責接收,根據此請求的URL , HTTP.SYS 將其傳遞給此ASP.NET 應用程式所對應的應用程式集區,由在此應用程式集區中執行的工作者進程負責處理請求[1] 。
工作者程序接收到這個請求之後,裝載專用於處理ASP.NET 頁面的一個ISAPI 擴充“ aspnet_isapi.dll ”,並將HTTP 請求傳給它。
工作者程序載入完aspnet_isapi.dll 後,由aspnet_isapi.dll 負責載入ASP.NET 應用程式的執行環境―― CLR [2] 。
工作者處理工作於非託管環境(指Windows 作業系統本身)之中,而.NET 中的物件則工作於託管環境(指CLR )之中, aspnet_isapi.dll 起到了一個溝通兩者的橋樑作用,將收到的HTTP 請求(由非託管環境傳來)轉送至對應.NET 物件(處於託管環境中)處理。
2 建立ApplicationManager 物件和應用程式網域
載入CLR 之後,由ApplicationManager 類別負責建立一個應用程式網域。每個ASP.NET 應用程式都運行於自己的應用程式網域中,並由唯一的應用程式識別碼識別。
每個應用程式網域都對應著一個ApplicationManager 類別的實例,由它來負責管理執行在網域中的ASP.NET 應用程式(例如啟動和停止一個ASP.NET 應用程序,在指定的ASP.NET 應用程式中創建物件等等)。
3 建立HostingEnvironment 對象
在為ASP.NET 應用程式建立應用程式網域的同時,會建立一個HostingEnvironment 對象,此物件提供了ASP.NET 應用程式的一些管理資訊(例如ASP.NET 應用程式的標識,對應的虛擬目錄和實體目錄),並提供了一些附加的功能(例如在應用程式網域中註冊一個對象,模擬特定的使用者等等)。
4 為每個請求建立ASP.NET 核心對象
當應用程式域建立完成之後,一個ISAPIRuntime 物件被創建,並自動呼叫它的ProcessRequest() 方法。在此方法中, ISAPIRuntime 物件會根據傳入的HTTP 請求建立一個HttpWorkerRequest 對象,此物件以物件導向的方式包裝了HTTP 請求的各種資訊(這就是說, 原始的HTTP 請求資訊被封裝為HttpWorkerRequest 物件)。然後,呼叫ISAPIRuntime 物件的StartProcessing() 方法啟動整個HTTP 請求處理過程(此即「 HTTP 管線: HTTP Pipeline 」 ),在這個處理過程的開端,一個HttpRuntime 類型的物件被創建,前面創建好的HttpWorkerRequest 物件作為方法參數被傳送給此HttpRuntime 物件的ProcessRequest() 方法。
在HttpRuntime 類別的ProcessRequest() 方法中完成了一些非常重要的工作,其中與Web 軟體工程師關係最緊密的是:
HttpRuntime 類別的ProcessRequest() 方法根據HttpWorkerRequest 物件中所提供的HTTP 請求訊息,建立了一個HttpContext 物件。
HttpContext 物件之所以重要,是因為此物件包容了另兩個在ASP.NET 程式設計中非常常見的物件: HttpResponse 和HttpRequest 。
HttpRequest 物件中的資訊來自於原始的HTTP 請求,例如它的Url 屬性就代表了原始HTTP 請求資訊中的URL 。
而HttpResponse 物件則擁有一些屬性和方法,用來產生要傳回給瀏覽器的資訊。
Page 類別提供了對應的屬性來引用這兩個對象,因此在ASP.NET 網頁中可以直接使用「 Requset 」和「 Response 」屬性來存取這兩個物件。例如:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response .Write(Request .Url);
}
}
Page 類別的Context 屬性引用HttpContext 對象,因此,上述程式碼也可以改寫為以下形式:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Context.Response .Write(this.Context.Request .Url);
}
}
關於HttpContext 、 HttpResponse 和HttpRequest 這三個對象,必須掌握以下的要點:
l HttpContext 物件包容HttpResponse 和HttpRequest 這兩個對象,可以從HttpRequest 物件取得HTTP 請求的相關訊息,而要向瀏覽器輸出的內容可以透過呼叫HttpResponse 的方法實作。
l 針對每個HTTP 請求, ASP.NET 都會建立一個HttpContext 對象,在整個HTTP 處理過程中,此對像都是可以存取的。
5 分配一個HttpApplication 物件用於處理請求
HttpRuntime 類別的ProcessRequest() 方法除了建立HttpContext 物件之外,還完成了另一個很重要的工作-向HttpApplicationFactory 類別的一個實例[3] 申請分配一個HttpApplication 物件用於管理整個HTTP 物件處理管線中的各請求處理管線中的各請求種事件。
HttpApplicationFactory 物件負責管理一個HttpApplication 物件池[4] ,當有HTTP 請求到來時,如果池中還有可用的HttpApplication 對象,就直接分配此物件用於處理HTTP 請求,否則,建立一個新的HttpApplication 對象。
6 HttpApplication 物件啟動HTTP 管線
HttpApplication 物件負責組裝整個“ HTTP 請求處理管線(HTTP Pipeline ) ”,可以將“ HTTP 請求處理管線”與現代工廠中的“生產管線”做個類比。前面步驟中創建好的HttpContext 物件就是這個生產流水線要加工的“產品”,當它流經“生產管線”的不同部分時,將被進行特定的加工和處理過程。
這些特定的「加工和處理過程」是怎麼進行的?
簡單來說,當 HttpContext 物件經過「生產管線」的不同部分時, HttpApplication 物件會先後激發出一連串的事件[5] 。一種特定的元件—— HTTP 模組( HTTP Module )可以回應這些事件,在此事件回應程式碼中可以對HttpContext 物件進行“加工和處理”,從這個意義上說, HTTP 模組可以看成是“生產管線」中的工人。 HTTP 模組其實就是前面所介紹過的「 ISAPI 篩選器」。
HTTP 模組物件是在HttpApplication 物件的InitModules() 方法[6] 中被建立的,我們一般在HTTP 模組物件Init() 方法[7] 中書寫程式碼使其可以回應HttpApplication 物件所激發的特定事件。
ASP.NET 提供了一些預先定義的HTTP 模組回應特定的事件, Web 軟體工程師也可以編寫自己的HTTP 模組並將其插入到「 HTTP 請求處理管線」中[8] 。
在管線的中部(處理完了相關的事件), HttpContext 物件被最終的Page 物件所接收(這就是為何可以在ASP.NET 頁面中透過Page 類別定義的Context 屬性存取HttpContext 物件的原因)。
每個被造訪的ASP.NET 頁面都會轉換為一個「 衍生自Page 類別的頁面類別」 。
注意: Page 類別實作了IHttpHandler 接口,此接口定義了一個ProcessRequest() 方法。
ASP.NET 頁面類別產生以後會自動編譯為程式集,然後其ProcessRequest() 方法被自動呼叫(因為Page 類別實作了IHttpHandler 接口,所以肯定有此方法)。在此方法中, Web 軟體工程師編寫的程式碼被執行(如果有的話)。 ProcessRequest() 方法的執行結果再次被HttpContext 物件所承載,控制又轉回「 HTTP 請求處理管線」中, HttpApplication 物件繼續激發後繼的事件。這時,如果還有特定的HTTP 模組回應這些事件,則它們會自動呼叫。
HttpContext 物件帶著最後的處理結果來到了「 HTTP 請求處理管線」的未端,其資訊被取出來,再次以aspnet_isapi.dll 為橋樑傳送給工作者進程。工作者進程再將HTTP 請求的處理結果轉給HTTP.SYS ,由它負責將結果傳回瀏覽器。
根據前面的介紹,整個Http 管線可以分成三段:預處理階段à 處理階段à 後處理階段( 圖8 ‑14 )。
圖8 ‑ 14 HTTP 管線的三個階段
如圖8 ‑14 所示, HTTP 管線的預處理和後處理階段主要由多個HTTP 模組參與,透過事件來驅動,這兩個階段完成的工作主要是對HttpContext 物件的各種屬性進行修改。
對HTTP 請求的處理過程最終是由一個實作IHttpHandler 介面的物件在「處理階段」完成的。每一個ASP.NET 網頁產生的頁面類別都實作了此介面。創建合適的HTTP 請求處理對象的工作由PageHandlerFactory 物件[9] 負責完成。
由此可見,實現了IHttpHandler 介面的物件負責處理HTTP 請求,這就是它被稱為「 Handler (處理程序)」的原因。
除了最常見的ASP.NET 網頁之外, Web 軟體工程師還可以建立自己的實作了IHttpHandler 介面的對象,並將其插入到HTTP 管線中以處理HTTP 請求。
當HTTP 請求處理完畢,相關的物件被釋放,但創建的應用程式域,以及HttpApplication 等物件仍然存活,以回應下一次HTTP 請求。
7 ASP.NET 應用程式生命週期小結
本節介紹了ASP.NET 應用程式的生命週期,這是一個相當複雜的過程,也許用以下通俗的類比更容易理解:
l “ HTTP 請求處理管線”就是一條現代工廠中的“生產流水線”, HttpContext 物件就是這條管線上要加工的產品。
l HttpHandler ( HTTP 處理程序)物件是整個「產品生產線」的核心,由它負責將產品組裝成形。
l HttpModule ( HTTP 模組)相當於「生產線」上的輔助工人,他們對產品( HttpContext 物件)進行「預處理」(為組裝產品作準備)和「後處理」(為產品出廠作準備,例如貼商標)。
l HttpApplication 物件是整個「生產線」的「領導」 ,他負責給「生產線」分配工人(初始化並裝載所有註冊的HttpModule ),然後會激發一系列的事件(被稱為「 ASP.NE T 應用程式事件」),特定的HttpModule 負責回應特定的事件。
-------------------------------------------------- ------------------------------
[1] 如果工作者進程不存在,則IIS 監控程式WAS 會建立一個,否則,重複使用現有的工作者進程。
[2] IIS 7 整合模式下,由於CLR 是預先載入的,所以這一步就不需要了。
[3] 「類別的實例」與「類別的物件」意義等同,都是指以類別為範本建立出來的物件。
[4] 物件池( object pool )是物件導向軟體系統常見的一種物件組織方式,可以將其視為一個物件容器。物件池中放有事先創建好的多個物件。當外界需要某個物件時,可以直接從池中取出一個現成的物件使用,這就避免了頻繁創建物件所帶來的效能損失。
[5] HttpApplication 定義了相當多的事件,完整的事件清單請查看MSDN 。
[6] 此方法會在取得HttpApplication 物件時自動呼叫。
[7] 所有HTTP 模組都要實現IHttpModule 接口, Init() 方法由此接口所定義。
[8] 透過在Web.Config 中插入特定的內容可以將自訂的HTTP 模組加入到HTTP 請求的處理流程中。
[9] 這是ASP.NET 技術架構中的另一個核心