最近看了幾篇講述httpHandler和HttpModuler的文章,總的來說還是Fish li的那篇文章給力,但是他是大牛,他寫出來的文章技術含量太高,對於像我這樣的小兵,
要完全看懂估計需要看幾遍。雖然說沒有完全瞭解底層操作,但是我也算明白了一個請求從進入IIS到最後輸出都經曆了哪些過程。說實話,原來我以為.Net的類的子
類都是設計者自己設計的,沒有考慮到真正的程式員是否可以完全掌握。瞭解了底層操作,我發現我的那個觀點是多麼的無知,每個.Net的類都是對應現實中的一種對
象,比如說Mvc3 中的路由就包括RouteData和HttpContext,至於為什麼要這樣包含?只有瞭解了iis的觸發過程,我們就會真正的瞭解了。
序言介紹完畢,現在就來分享一下我對IIS底層的理解。技術不過硬,只能是採用大白話來說。
上面這幅圖說明了在IIS 6 下的處理過程。
因為我們現在所用的是MVC3 ,所以我就按照MVc3 中的生命週期來敘述一個請求從開始到消亡的全過程。
要求階段:
使用者通過瀏覽器輸入localhost/home/index 的地址,瀏覽器會發送一個請求到伺服器的IIS用來處理這個請求。其實在作業系統中存在一個系統檔案叫做http.sys
檔案,它用來監視是否有請求到來,也就是說一個使用者發來的請求的第一個接待者就是http.sys,它是一個系統檔案,運行在作業系統的核心模式下,因此運行速度
更快。
在http.sys檔案接收到請求之後(註:這是我的一個理解誤區,我以前一直以為請求會直接進入IIS),會傳入到第二個接待者IIS,真正的用來處理請求的作業系統組
件。在IIS接收到使用者請求以後,首先會通過對應檔 然後由aspnet_iisapi.dll (IIS擴充)根據副檔名來選擇對應的應用程式。這樣說有點拗口,直白點的意思
就是IIS擴充會根據傳入檔案的副檔名(.aspx等)來選擇 在IIS中配置的處理常式。這裡會有一個問題存在,在Mvc中沒有副檔名,那麼程式是如何匹配的呢?其實這個
問題的處理方法有兩種:
1.就是通過在路由表中添加一個虛擬副檔名來欺騙IIS
2.就是通過在IIS設定檔中不選擇確認檔案存在,讓IIS根據沒有副檔名的檔案路徑來進行處理
現在這個請求到了哪裡?到了IIS擴充這裡,下一步就是要進入到.Net架構中,讓.Net架構來處理請求。但是在這中間會經過一些步驟的處理。大家應該記得在Web
form中有很多的事件,Page_load、Page_Render等,這些事件的執行順序是依次進行的,不會混亂?那麼.Net架構是如何來保證這些事件的順序執行呢?這就是今
天的第一個主角HttpModule。我們可以把它稱為http請求的過濾器,因為它不會有任何的輸出,它會在任何請求中都會執行。當然有一個例外,那就是靜態檔案或者
其他沒有配置為讓IIS擴充讓.Net架構處理的請求檔案,因為他們進入到IIS中,IIS會找到對應的檔案然後輸出給瀏覽器的。
HttpModule的具體使用大牛們都說的很清楚了,我就簡潔的描述一下大牛們忽略的知識點。既然說HttpModule是一個過濾器,那麼我們可以在任何一個HttpModule
中終止當前請求的執行,執行身份認證,請問檔案的存取權限檢查等操作。我們可以自訂HttpModule擴充,只是讓我們自己定義的類實現IHttpModule介面即可,
在IHttpModule 介面中有一個Init(HttpApplication app)方法,這是我們自訂擴充Module的入口,我們可以在其中定義我們自己進行的處理操作。
Init這個方法會接受一個HttpApplication類型的參數,HttpApplication 在MSDN中的定義就是定義 ASP.NET 應用程式中的所有應用程式物件通用的方法、屬性和事
件。此類是使用者在 global.asax 檔案中所定義的應用程式的基類。HttpApplication 類的執行個體是在 ASP.NET 基礎結構中建立的,而不是由使用者直接建立的。
HttpApplication 類的一個執行個體在其生存期內被用於處理多個請求,但它一次只能處理一個請求。這樣,成員變數才可用於儲存針對每個請求的資料。
看到這個類的定義我們有沒有想到應用程式集區的概念,在IIS中我們建立一個應用程式就會建立一個對應的應用程式集區,其實在應用程式集區中儲存的是什嗎?應該就是
這些HttpApplication對象。每個請求會有一個對應的HttpApplication對象來全程的負責它的執行,在httpApplication對象中包含著請求所需要的所有參數值。例如
Response、Request、Cache等.Net常用的對象,甚至我們可以通過這個變數擷取到web.config中定義的所有Module擴充。HttpApplication會伴隨著請求的全部
執行過程。
現在一個問題又來了,這個Module擴充需要傳遞一個HttpApplication對象作為參數,那麼這個方法的參數是由誰建立的呢?我們應該經常用到一個類
HttpRuntime,根據這個字面意思,我們也可以想到這個就是表示的Http運行時,是的,在IIS將請求的資料準備好以後會通過HttpRuntime 調用
HttpApplicationFactory的一個Create()方法來得到一個HttpApplication對象,然後把參數值傳遞給這個對象,最後這個對象會傳遞到Module擴充中。
現在請求經過了Module擴充過濾之後,就要進入到真正處理它的地方了,HttpHandler,提起它,如果我們有點陌生,那麼我們一定使用過.Net中的一般處理常式,
我們可以看到一般處理常式是一個ashx檔案,其中會繼承自IHttpHandler介面,進行ProcessRequest處理。其實我們的HttpHandler就是ashx檔案的codeBehind
檔案。只要我們實現了IHttpHandler介面中的方法,就定義了一個Handler擴充。
HttpHandler 是作為處理者的角色出現的,不是過濾者,所以Handler會有輸出結果。如果你要在Handler中使用Session,那麼就要繼承IRequiredSessionState
介面,或者加上一個IReadOnlySessionState介面,這樣我們操作Session的時候才不會出現錯誤。在Handler中我們可以進行任何我們想要的操作,例如產生圖片
浮水印、防盜鏈甚至是檔案的輸出壓縮以及編碼等都可以實現。
像我們的Web Service以及一般處理常式,從本質上說都是Handler的一種高層實現方式,都是進行了Handler的擴充操作。
因為我們討論的是MVC,所以我們不得不考慮路由Route,其實Route是Mvc中的一個單獨的組件,它在我們的整個請求中也佔據了非常重要的地位。在IIS通過IIS擴
展選擇了適當的處理常式來處理這個請求的時候,就是路由出現的時候,路由會根據路由配置分析這個路徑的ControllerName以及ActionName,對應的參數值,然
後會把這些參數儲存到RouteData中,RouteTable.Routes 是一個路由集合,RouteData和HttpContext上下文就會組成另一個類的對象,RequestContext,我
們在MVC編程的時候,經常會用到這個對象中的一些資料。.Net架構會根據RequestContext對象的值來匹配程式中的Controller以及Action,然後調用
ControllerDescriptor 執行Controller,產生Controller的對象,然後通過ActionInvoke方法來執行具體的Action。
在Action執行完畢,返回對應的視圖的時候,整個請求在.Net架構中的處理就算結束了。在輸出結果返回到使用者瀏覽器之前,輸出結果還會經過Module擴充的最後處
理,輸出結果到達IIS,最後IIS通過Http.sys響應到使用者瀏覽器上,使用者就可以看到輸出結果。
因為一個請求從進入到顯示在瀏覽器上會兩次經過Module擴充,這就是為什麼我們在web form中可以定義一個開始事件,然後還會有一個完成事件的原因。
總結一下,一個使用者發起的請求通過http.sys-->IIS-->aspnet_iisapi.dll-->對應的處理常式-->Module--->Handler-->Module--->IIS-->http.sys-->使用者瀏覽器。
當然這個請求的順序不是特別的準確,因為省略了好多的細節,但是從大的方面說就是這些功能。可能你會有一個疑問,aspx檔案沒有是什麼時候執行的呢?其實這
個問題我以前也有想過,aspx是在Module之後處理的,但是在handler之後還是之前呢?今天終於得到了答案,其實一個單獨的aspx檔案就是一個handler,每個
aspx檔案在編譯的時候都會編譯成一個類,這個類繼承自Page,但是Page繼承自哪裡呢?
複製代碼 代碼如下:public class Page : TemplateControl, IHttpHandler
我們可以看到Page繼承自IHttpHandler介面,這就驗證了Page類的執行是在Handler執行的時候觸發的。
一個小小的http請求會讓我們有那麼多的知識要掌握,我們作為程式員對於這個請求的模型應該是很熟悉。但是作為.Net 拖控制項開發的程式員,我善意的提醒一下,
如果可以不用控制項,我們就別用了,用js、css來代替吧,畢竟html是基礎。在Mvc時代到來的時候,,擁抱新技術吧。
我是小兵,沒有太多的發言權,所以我就是按小兵的思路來分析大牛們的技術。