選擇HttpHandler還是HttpModule?


最近收到幾個疑問:HttpHandler和HttpModule之間有什么差別,我到底該選擇哪個?
之所以有這個疑問,是因為在這二類對象中都可以訪問Request, Response對象,都能處理請求。

我原以為在博客 用Asp.net寫自己的服務框架 中做了那么多的演示應該把它們的使用方法說清楚了, 然而有些人看了我的那些示例,仍然不知道該如何選擇它們,為了實現同一個目標,我既用了HttpHandler,也有用HttpModule。 現在看來,我當時設計的那些示例並不是講清楚HttpHandler和HttpModule之間有什么差別, 而是在演示如何利用HttpHandler和HttpModule設計一個服務框架。

很慶幸那篇博客內容沒有走題,今天只好再來寫一篇了。

本文約定:
1. HttpHandler泛指所有實現IHttpHandler接口的類型。
2. HttpModule泛指所有實現IHttpModule接口的類型。
因此,本文將不會特別區分這些類型與接口。

理解ASP.NET管線

HttpHandler和HttpModule,它們都與ASP.NET管線有關,所以我想理解這二類對象必須要理解ASP.NET管線的工作方式。

下圖反映了ASP.NET管線的處理流程:
ASP.NET管線處理流程

這是一張時序圖,我們應該從二個角度來理解它:
1. 有哪些調用動作。
2. 有哪些參與者。

每個調用動作,都反映了ASP.NET管線的處理階段,它們會引發相應的事件(除GetHandler,ProcessRequest之外), HttpModule則會訂閱這些事件,參與到管線處理過程。 這些階段中,有些階段還引發了二個事件,完整的管線事件可參考MSDN文檔:

圖片中還反映了ASP.NET的三種主要的參與者:
1. HttpModule
2. HttpHandlerFactory
3. HttpHandler

有沒有有想過:這三種參與者中,每種有多少個參與對象呢?

為了清楚地回答這個問題,我准備了下面的表格:

管線參與者 每次請求中參與者數量
HttpModule >= 0
HttpHandlerFactory 1
HttpHandler 1

為什么要引入HttpHandlerFactory呢? 請看我的博客 細說 HttpHandler 的映射過程,今天就不重復這塊內容了。

除開HttpHandlerFactory,我們可以發現:在ASP.NET管線中,HttpHandler應該只有一個,而HttpModule是可選的。
進而,我們是不是可以這樣理解:HttpHandler才是處理請求的主角(不可缺少),HttpModule是配角(可以沒有)?

理解HttpApplication

前面我們一直在說ASP.NET管線,那么,誰在控制管線過程?
答案是:HttpApplication對象。
1. HttpApplication細分它的處理過程,在不同階段引發不同的事件,使得HttpModule通過訂閱事件的方式加入到請求的處理過程中。
2. 在請求的處理過程中,HttpApplication對象主要扮演着控制處理流程的推進作用。
3. HttpApplication會在固定的階段獲取一個IHttpHandler實例,然后將請求的響應過程交給具體的IHttpHandler來實現。

HttpApplication如何產生,如何工作?
1. HttpApplication對象會被重用,當HttpRuntime不能從HttpApplicationFactory獲取空閑的實例時,才會創建。
2. HttpRuntime會將每個請求交給一個HttpApplication對象來處理。
3. HttpApplication對象在初始化時負責加載全部的HttpModule。
4. 每個HttpApplication對象會控制屬於它的管線過程(前面已解釋)。

HttpApplication是個非常重要的類型,它的許多功能都屬於框架的基礎部分,不需要我們調用, 因此,我們平時不會用到它。

我不想讓博客走題,下面來看看今天的主角吧。

理解HttpHandler

前面說到HttpRuntime會將請求交給HttpApplication來處理, 此時你有沒有想過這樣一個問題:為什么HttpApplication不直接處理請求,而是要再交給一個HttpHandler對象來處理呢?

答案是:每個請求的內容可能並不相同,它們存在多樣性,因此ASP.NET采用了抽象工廠模式來處理這些請求。 ASP.NET在web.config的架構中,允許我們指定某些請求映射到一個HttpHandlerFactory,例如:

<!--適用於IIS6的配置-->
<system.web>
    <httpHandlers>
        <add path="*.cspx" verb="*" type="MyMVC.AjaxHandlerFactory, MyMVC" validate="true" />
        <add path="*.aspx" verb="*" type="MyMVC.MvcPageHandlerFactory, MyMVC" validate="true" />
        <add path="/mvc/*" verb="*" type="MyMVC.MvcPageHandlerFactory, MyMVC" validate="true" />
    </httpHandlers>
</system.web>

<!--適用於IIS7的配置(集成模式)-->
<system.webServer>
    <handlers>
        <add name="AjaxHandlerFactory" verb="*" path="*.cspx" type="MyMVC.AjaxHandlerFactory, MyMVC" preCondition="integratedMode" />
        <add name="MvcPageHandlerFactory" verb="*" path="*.aspx" type="MyMVC.MvcPageHandlerFactory, MyMVC" preCondition="integratedMode" />
        <add name="MvcPageHandlerFactory2" verb="*" path="/mvc/*" type="MyMVC.MvcPageHandlerFactory, MyMVC" preCondition="integratedMode" />
    </handlers>
</system.webServer>

當某個請求與一個規則匹配后,ASP.NET會調用匹配的HttpHandlerFactory的GetHandler方法來獲取一個HttpHandler實例, 最后由一個HttpHandler實例來處理當前請求

HttpApplication是如何將請求交給HttpHandler實例來處理的呢?
為了理解這個過程,我們要來看一下IHttpHandler接口的定義:

// 這個接口用於同步調用
// 異步版本的接口用法請參考:http://www.cnblogs.com/fish-li/archive/2011/11/20/2256385.html

public interface IHttpHandler
{
    // 獲取一個值,該值指示其他請求是否可以使用 IHttpHandler 實例。
    bool IsReusable { get; }

    // 通過實現 IHttpHandler 接口的自定義 HttpHandler 啟用 HTTP Web 請求的處理。
    void ProcessRequest(HttpContext context);
}

HttpApplication在將某個請求交給HttpHandler實例來處理時,是通過接口來調用的(ProcessRequest方法)。

與HttpHandler的相關話題:
1. 異步 HttpHandler:細說ASP.NET的各種異步操作
2. 如何重用HttpHandler:細說 HttpHandler 的映射過程


HttpHanlder的典型應用

通常我去這樣創建一個ashx文件(HttpHanlder)響應某種特殊的請求。
所以,我們應該這樣理解HttpHanlder:一個HttpHanlder用於響應一類特定的請求。

我們經常用到的HttpHanlder有哪些?

我們通常使用HttpHanlder做什么?

理解HttpModule

設計HttpHanlder的目的很明確:生成響應結果。
那么,設計HttpModule又是為什么呢?

前面說過,一個HttpHanlder用於處理一類特定的請求,每個aspx, ashx都可以認為是一類請求。 有時候我們發現所有頁面可能都需要某些相同的檢查功能(如身份檢查), 假如只能使用HttpHanlder,那我們就要讓所有頁面都去調用那些相同的檢查功能。 誰願意做這些重復的事情? 或許有些人會回答,可以自己實現一個基類,把檢查功能放在基類中去調用。 然而,這種做法只能解決重復調用問題,它會讓代碼失去靈活性(擴展性), 試想一下:如果需要再加入新的檢查功能,或者用新的檢查方法替換原有的檢查邏輯時怎么辦? 只能是修改基類了吧?

設計HttpModule的目的正是為了提供一個靈活的方法解決這種功能重用問題。 它采用事件(觀察者)的設計模式,將某些HttpHanlder都需要的功能抽取出來, 形成不同的觀察者類型,這些觀察者類型可以編譯成類庫形式,供多個網站項目共用。 為了讓ASP.NET管線更靈活,ASP.NET允許我們在web.config中自由配置需要的HttpModule,例如:

<!--適用於IIS6的配置-->
<system.web>
    <httpModules>
        <add name="SetOutputCacheModule" type="MyMVC.SetOutputCacheModule, MyMVC"/>
    </httpModules>
</system.web>

<!--適用於IIS7的配置(集成模式)-->
<system.webServer>
    <modules>
        <add name="SetOutputCacheModule" type="MyMVC.SetOutputCacheModule, MyMVC" preCondition="integratedMode" />
    </modules>
</system.webServer>

配置只是告訴ASP.NET:這些HttpModule需要運行起來。 有沒有想過這些HttpModule到底是如何進入管線運行起來的呢? 前面我只是說了HttpModule會訂閱這些事件,那么事件又是在哪里訂閱的呢? 還是來看一下IHttpModule接口的定義吧:

// 這個接口用於同步調用
// 異步用法請參考:http://www.cnblogs.com/fish-li/archive/2011/11/20/2256385.html

public interface IHttpModule
{
    //  初始化模塊,並使其為處理請求做好准備。
    void Init(HttpApplication app);

    void Dispose();
}

注意這個關鍵的Init方法,它傳入一個HttpApplication類型的參數,有了HttpApplication對象,HttpModule就可以訂閱HttpApplication的所有事件了。 請看下面的示例代碼:

HttpModule的典型應用

這個Module用於給一些在配置文件中指出要緩存的請求設置輸出緩存,示例代碼已在上篇博客 不修改代碼就能優化ASP.NET網站性能的一些方法 介紹過了。 其實設置輸出緩存的最根本手段還是調用Response.Cache的一些公開方法,修改輸出響應頭。

我們用HttpModule做什么事情?

HttpModule能處理哪些請求呢?

三大對象的總結

前面我分別介紹了HttpApplication,HttpHanlder和HttpModule,這里再把三者的關系重新梳理一遍。

在請求的處理過程中,HttpApplication對象主要扮演着控制管線處理流程的作用,它負責推進整個處理流程, 除了在不同階段引發不同的事件外(供HttpModule使用),HttpApplication對象還會根據當前請求尋找一個合適的IHttpApplicationFactory實例, 並最終得到一個IHttpHandler的實例用於處理請求。

設計這三種類型的目的在於:
1. HttpApplication控制處理流程,在不同階段引發不同的事件。
2. 由於請求的多樣性,每個請求會由一個HttpHandler對象來處理。
3. 對於一些通用性的功能,尤其是與響應內容無關的,設計成HttpModule是最合適的。

案例演示

Q:我有一些html文件,需要做身份認證檢查(判斷Session),我該如何實現?

想好了就來看看我的解決方案:

Q:我需要壓縮所有的ASP.NET請求的響應結果,該怎么實現?

想好了就來看看我的解決方案:

如何選擇?

在結束這篇博客之前,再問問各位讀者:現在知道何時選擇HttpHandler還是HttpModule了嗎?

如果還沒有看明白,那我就最后告訴你一個識別方法:
1. 如果要響應一類請求,那么就選擇HttpHandler。
2. 如果要修改或者檢查所有請求(總之就是不生成響應結果),那就選擇HttpModule。


最后給各位留下一個題目,下面這些ASP.NET提供的功能,它們是采用了哪個方式實現的?
1. Session
2. 身份認證
3. URL授權檢查
3. 通過trace.axd查看跟蹤信息
4. OutputCache
5. 禁止下載config文件
6. 禁止查看下載源代碼文件

注意:本文的主題是:選擇HttpHandler還是HttpModule,所以請不要扯遠了。




2013年第一篇博客,希望所有讀者:萬事如意,身體健康!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM