選擇HttpHandler還是HttpModule?
最近收到幾個(gè)疑問(wèn):HttpHandler和HttpModule之間有什么差別,我到底該選擇哪個(gè)?
之所以有這個(gè)疑問(wèn),是因?yàn)樵谶@二類對(duì)象中都可以訪問(wèn)Request, Response對(duì)象,都能處理請(qǐng)求。
我原以為在博客 用Asp.net寫(xiě)自己的服務(wù)框架 中做了那么多的演示應(yīng)該把它們的使用方法說(shuō)清楚了,然而有些人看了我的那些示例,仍然不知道該如何選擇它們,為了實(shí)現(xiàn)同一個(gè)目標(biāo),我既用了HttpHandler,也有用HttpModule?,F(xiàn)在看來(lái),我當(dāng)時(shí)設(shè)計(jì)的那些示例并不是講清楚HttpHandler和HttpModule之間有什么差別,而是在演示如何利用HttpHandler和HttpModule設(shè)計(jì)一個(gè)服務(wù)框架。
很慶幸那篇博客內(nèi)容沒(méi)有走題,今天只好再來(lái)寫(xiě)一篇了。
本文約定:
1. HttpHandler泛指所有實(shí)現(xiàn)IHttpHandler接口的類型。
2. HttpModule泛指所有實(shí)現(xiàn)IHttpModule接口的類型。
因此,本文將不會(huì)特別區(qū)分這些類型與接口。
理解ASP.NET管線
HttpHandler和HttpModule,它們都與ASP.NET管線有關(guān),所以我想理解這二類對(duì)象必須要理解ASP.NET管線的工作方式。
下圖反映了ASP.NET管線的處理流程:
這是一張時(shí)序圖,我們應(yīng)該從二個(gè)角度來(lái)理解它:
1. 有哪些調(diào)用動(dòng)作。2. 有哪些參與者。
每個(gè)調(diào)用動(dòng)作,都反映了ASP.NET管線的處理階段,它們會(huì)引發(fā)相應(yīng)的事件(除GetHandler,ProcessRequest之外), HttpModule則會(huì)訂閱這些事件,參與到管線處理過(guò)程。這些階段中,有些階段還引發(fā)了二個(gè)事件,完整的管線事件可參考MSDN文檔:
- 在處理該請(qǐng)求時(shí)將由 HttpApplication 類執(zhí)行以下事件。 希望擴(kuò)展 HttpApplication 類的開(kāi)發(fā)人員尤其需要注意這些事件。
- 1. 對(duì)請(qǐng)求進(jìn)行驗(yàn)證,將檢查瀏覽器發(fā)送的信息,并確定其是否包含潛在惡意標(biāo)記。 有關(guān)更多信息,請(qǐng)參見(jiàn) ValidateRequest 和腳本侵入概述。
- 2. 如果已在 Web.config 文件的 UrlMappingsSection 節(jié)中配置了任何 URL,則執(zhí)行 URL 映射。
- 3. 引發(fā) BeginRequest 事件。
- 4. 引發(fā) AuthenticateRequest 事件。
- 5. 引發(fā) PostAuthenticateRequest 事件。
- 6. 引發(fā) AuthorizeRequest 事件。
- 7. 引發(fā) PostAuthorizeRequest 事件。
- 8. 引發(fā) ResolveRequestCache 事件。
- 9. 引發(fā) PostResolveRequestCache 事件。
- 10. 根據(jù)所請(qǐng)求資源的文件擴(kuò)展名(在應(yīng)用程序的配置文件中映射),選擇實(shí)現(xiàn) IHttpHandler 的類,對(duì)請(qǐng)求進(jìn)行處理。 如果該請(qǐng)求針對(duì)從 Page 類派生的對(duì)象(頁(yè)),并且需要對(duì)該頁(yè)進(jìn)行編譯,則 ASP.NET 會(huì)在創(chuàng)建該頁(yè)的實(shí)例之前對(duì)其進(jìn)行編譯。
- 11. 引發(fā) PostMapRequestHandler 事件。
- 12. 引發(fā) AcquireRequestState 事件。
- 13. 引發(fā) PostAcquireRequestState 事件。
- 14. 引發(fā) PreRequestHandlerExecute 事件。
- 15. 為該請(qǐng)求調(diào)用合適的 IHttpHandler 類的 ProcessRequest 方法(或異步版 IHttpAsyncHandler.BeginProcessRequest)。 例如,如果該請(qǐng)求針對(duì)某頁(yè),則當(dāng)前的頁(yè)實(shí)例將處理該請(qǐng)求。
- 16. 引發(fā) PostRequestHandlerExecute 事件。
- 17. 引發(fā) ReleaseRequestState 事件。
- 18. 引發(fā) PostReleaseRequestState 事件。
- 19. 如果定義了 Filter 屬性,則執(zhí)行響應(yīng)篩選。
- 20. 引發(fā) UpdateRequestCache 事件。
- 21. 引發(fā) PostUpdateRequestCache 事件。
- 22. 引發(fā) EndRequest 事件。
- 23. 引發(fā) PreSendRequestHeaders 事件。
- 24. 引發(fā) PreSendRequestContent 事件。
圖片中還反映了ASP.NET的三種主要的參與者:
1. HttpModule;2. HttpHandlerFactory;3. HttpHandler
有沒(méi)有有想過(guò):這三種參與者中,每種有多少個(gè)參與對(duì)象呢?
為了清楚地回答這個(gè)問(wèn)題,我準(zhǔn)備了下面的表格:
為什么要引入HttpHandlerFactory呢? 請(qǐng)看我的博客 細(xì)說(shuō) HttpHandler 的映射過(guò)程,今天就不重復(fù)這塊內(nèi)容了。
除開(kāi)HttpHandlerFactory,我們可以發(fā)現(xiàn):在ASP.NET管線中,HttpHandler應(yīng)該只有一個(gè),而HttpModule是可選的。
進(jìn)而,我們是不是可以這樣理解:HttpHandler才是處理請(qǐng)求的主角(不可缺少),HttpModule是配角(可以沒(méi)有)?
#p#
理解HttpApplication
前面我們一直在說(shuō)ASP.NET管線,那么,誰(shuí)在控制管線過(guò)程?
答案是:HttpApplication對(duì)象。
1. HttpApplication細(xì)分它的處理過(guò)程,在不同階段引發(fā)不同的事件,使得HttpModule通過(guò)訂閱事件的方式加入到請(qǐng)求的處理過(guò)程中。
2. 在請(qǐng)求的處理過(guò)程中,HttpApplication對(duì)象主要扮演著控制處理流程的推進(jìn)作用。
3. HttpApplication會(huì)在固定的階段獲取一個(gè)IHttpHandler實(shí)例,然后將請(qǐng)求的響應(yīng)過(guò)程交給具體的IHttpHandler來(lái)實(shí)現(xiàn)。
HttpApplication如何產(chǎn)生,如何工作?
1. HttpApplication對(duì)象會(huì)被重用,當(dāng)HttpRuntime不能從HttpApplicationFactory獲取空閑的實(shí)例時(shí),才會(huì)創(chuàng)建。
2. HttpRuntime會(huì)將每個(gè)請(qǐng)求交給一個(gè)HttpApplication對(duì)象來(lái)處理。
3. HttpApplication對(duì)象在初始化時(shí)負(fù)責(zé)加載全部的HttpModule。
4. 每個(gè)HttpApplication對(duì)象會(huì)控制屬于它的管線過(guò)程(前面已解釋)。
HttpApplication是個(gè)非常重要的類型,它的許多功能都屬于框架的基礎(chǔ)部分,不需要我們調(diào)用,因此,我們平時(shí)不會(huì)用到它。
我不想讓博客走題,下面來(lái)看看今天的主角吧。
#p#
理解HttpHandler
前面說(shuō)到HttpRuntime會(huì)將請(qǐng)求交給HttpApplication來(lái)處理,此時(shí)你有沒(méi)有想過(guò)這樣一個(gè)問(wèn)題:為什么HttpApplication不直接處理請(qǐng)求,而是要再交給一個(gè)HttpHandler對(duì)象來(lái)處理呢?
答案是:每個(gè)請(qǐng)求的內(nèi)容可能并不相同,它們存在多樣性,因此ASP.NET采用了抽象工廠模式來(lái)處理這些請(qǐng)求。 ASP.NET在web.config的架構(gòu)中,允許我們指定某些請(qǐng)求映射到一個(gè)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>
當(dāng)某個(gè)請(qǐng)求與一個(gè)規(guī)則匹配后,ASP.NET會(huì)調(diào)用匹配的HttpHandlerFactory的GetHandler方法來(lái)獲取一個(gè)HttpHandler實(shí)例, 最后由一個(gè)HttpHandler實(shí)例來(lái)處理當(dāng)前請(qǐng)求。
HttpApplication是如何將請(qǐng)求交給HttpHandler實(shí)例來(lái)處理的呢?
為了理解這個(gè)過(guò)程,我們要來(lái)看一下IHttpHandler接口的定義:
- // 這個(gè)接口用于同步調(diào)用
- // 異步版本的接口用法請(qǐng)參考:http://www.cnblogs.com/fish-li/archive/2011/11/20/2256385.html
- public interface IHttpHandler
- {
- // 獲取一個(gè)值,該值指示其他請(qǐng)求是否可以使用 IHttpHandler 實(shí)例。
- bool IsReusable { get; }
- // 通過(guò)實(shí)現(xiàn) IHttpHandler 接口的自定義 HttpHandler 啟用 HTTP Web 請(qǐng)求的處理。
- void ProcessRequest(HttpContext context);
- }
HttpApplication在將某個(gè)請(qǐng)求交給HttpHandler實(shí)例來(lái)處理時(shí),是通過(guò)接口來(lái)調(diào)用的(ProcessRequest方法)。
與HttpHandler的相關(guān)話題:
1. 異步 HttpHandler:細(xì)說(shuō)ASP.NET的各種異步操作。
2. 如何重用HttpHandler:細(xì)說(shuō) HttpHandler 的映射過(guò)程。
HttpHanlder的典型應(yīng)用
- <%@ WebHandler Language="C#" Class="Login" %>
- using System;
- using System.Web;
- public class Login : IHttpHandler {
- public void ProcessRequest (HttpContext context) {
- context.Response.ContentType = "text/plain";
- string username = context.Request.Form["name"];
- string password = context.Request.Form["password"];
- if( password == "aaaa" ) {
- System.Web.Security.FormsAuthentication.SetAuthCookie(username, false);
- context.Response.Write("OK");
- }
- else {
- context.Response.Write("用戶名或密碼不正確。");
- }
- }
通常我去這樣創(chuàng)建一個(gè)ashx文件(HttpHanlder)響應(yīng)某種特殊的請(qǐng)求。
所以,我們應(yīng)該這樣理解HttpHanlder:一個(gè)HttpHanlder用于響應(yīng)一類特定的請(qǐng)求。
我們經(jīng)常用到的HttpHanlder有哪些?
- 1. aspx頁(yè)面。
- 2. asmx服務(wù)文件。
- 3. ashx文件(一般處理程序)。
- 4. 實(shí)現(xiàn)IHttpHandler接口的自定義類型。
我們通常使用HttpHanlder做什么?
注意觀察表格中實(shí)現(xiàn)目標(biāo),我們可以得到一個(gè)結(jié)論:使用HttpHanlder的目的是生成響應(yīng)結(jié)果。
#p#
理解HttpModule
設(shè)計(jì)HttpHanlder的目的很明確:生成響應(yīng)結(jié)果。
那么,設(shè)計(jì)HttpModule又是為什么呢?
前面說(shuō)過(guò),一個(gè)HttpHanlder用于處理一類特定的請(qǐng)求,每個(gè)aspx, ashx都可以認(rèn)為是一類請(qǐng)求。有時(shí)候我們發(fā)現(xiàn)所有頁(yè)面可能都需要某些相同的檢查功能(如身份檢查),假如只能使用HttpHanlder,那我們就要讓所有頁(yè)面都去調(diào)用那些相同的檢查功能。誰(shuí)愿意做這些重復(fù)的事情?或許有些人會(huì)回答,可以自己實(shí)現(xiàn)一個(gè)基類,把檢查功能放在基類中去調(diào)用。然而,這種做法只能解決重復(fù)調(diào)用問(wèn)題,它會(huì)讓代碼失去靈活性(擴(kuò)展性),試想一下:如果需要再加入新的檢查功能,或者用新的檢查方法替換原有的檢查邏輯時(shí)怎么辦?只能是修改基類了吧?
設(shè)計(jì)HttpModule的目的正是為了提供一個(gè)靈活的方法解決這種功能重用問(wèn)題。它采用事件(觀察者)的設(shè)計(jì)模式,將某些HttpHanlder都需要的功能抽取出來(lái),形成不同的觀察者類型,這些觀察者類型可以編譯成類庫(kù)形式,供多個(gè)網(wǎng)站項(xiàng)目共用。為了讓ASP.NET管線更靈活,ASP.NET允許我們?cè)趙eb.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需要運(yùn)行起來(lái)。有沒(méi)有想過(guò)這些HttpModule到底是如何進(jìn)入管線運(yùn)行起來(lái)的呢?前面我只是說(shuō)了HttpModule會(huì)訂閱這些事件,那么事件又是在哪里訂閱的呢?還是來(lái)看一下IHttpModule接口的定義吧:
- // 這個(gè)接口用于同步調(diào)用
- // 異步用法請(qǐng)參考:http://www.cnblogs.com/fish-li/archive/2011/11/20/2256385.html
- public interface IHttpModule
- {
- // 初始化模塊,并使其為處理請(qǐng)求做好準(zhǔn)備。
- void Init(HttpApplication app);
- void Dispose();
- }
注意這個(gè)關(guān)鍵的Init方法,它傳入一個(gè)HttpApplication類型的參數(shù),有了HttpApplication對(duì)象,HttpModule就可以訂閱HttpApplication的所有事件了。請(qǐng)看下面的示例代碼:
- public class TestModule : IHttpModule
- {
- public void Dispose() {}
- public void Init(HttpApplication app)
- {
- app.PostAcquireRequestState += new EventHandler(app_PostAcquireRequestState);
- app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);
- }
- void app_PreRequestHandlerExecute(object sender, EventArgs e)
- {
- throw new NotImplementedException();
- }
- void app_PostAcquireRequestState(object sender, EventArgs e)
- {
- throw new NotImplementedException();
- }
- }
HttpModule的典型應(yīng)用
- public class SetOutputCacheModule : IHttpModule
- {
- public void Init(HttpApplication app)
- {
- app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);
- }
- void app_PreRequestHandlerExecute(object sender, EventArgs e)
- {
- HttpApplication app = (HttpApplication)sender;
- Dictionary<string, OutputCacheSetting> settings = ConfigManager.Settings;
- if( settings == null )
- throw new ConfigurationErrorsException("SetOutputCacheModule加載配置文件失敗。");
- // 實(shí)現(xiàn)方法:
- // 查找配置參數(shù),如果找到匹配的請(qǐng)求,就設(shè)置OutputCache
- OutputCacheSetting setting = null;
- if( settings.TryGetValue(app.Request.FilePath, out setting) ) {
- setting.SetResponseCache(app.Context);
- }
- }
這個(gè)Module用于給一些在配置文件中指出要緩存的請(qǐng)求設(shè)置輸出緩存,示例代碼已在上篇博客 不修改代碼就能優(yōu)化ASP.NET網(wǎng)站性能的一些方法 介紹過(guò)了。其實(shí)設(shè)置輸出緩存的最根本手段還是調(diào)用Response.Cache的一些公開(kāi)方法,修改輸出響應(yīng)頭。
我們用HttpModule做什么事情?
1. 修改某些請(qǐng)求(例如前面的示例修改了響應(yīng)頭)。
2. 檢查檢查請(qǐng)求(例如身份認(rèn)證檢查)。
HttpModule能處理哪些請(qǐng)求呢?
1. 默認(rèn)是全部進(jìn)入ASP.NET的請(qǐng)求。
2. 如果只需要處理部分請(qǐng)求,那么請(qǐng)自行判斷(參考前面的示例)。
#p#
三大對(duì)象的總結(jié)
前面我分別介紹了HttpApplication,HttpHanlder和HttpModule,這里再把三者的關(guān)系重新梳理一遍。
在請(qǐng)求的處理過(guò)程中,HttpApplication對(duì)象主要扮演著控制管線處理流程的作用,它負(fù)責(zé)推進(jìn)整個(gè)處理流程,除了在不同階段引發(fā)不同的事件外(供HttpModule使用),HttpApplication對(duì)象還會(huì)根據(jù)當(dāng)前請(qǐng)求尋找一個(gè)合適的IHttpApplicationFactory實(shí)例,并最終得到一個(gè)IHttpHandler的實(shí)例用于處理請(qǐng)求。
設(shè)計(jì)這三種類型的目的在于:
1. HttpApplication控制處理流程,在不同階段引發(fā)不同的事件。
2. 由于請(qǐng)求的多樣性,每個(gè)請(qǐng)求會(huì)由一個(gè)HttpHandler對(duì)象來(lái)處理。
3. 對(duì)于一些通用性的功能,尤其是與響應(yīng)內(nèi)容無(wú)關(guān)的,設(shè)計(jì)成HttpModule是最合適的。
案例演示
Q:我有一些html文件,需要做身份認(rèn)證檢查(判斷Session),我該如何實(shí)現(xiàn)?
想好了就來(lái)看看我的解決方案:
- /// <summary>
- /// 用于響應(yīng)HTML文件的處理器
- /// </summary>
- public class HtmlHttpHandler : IHttpHandler, IRequiresSessionState, IReadOnlySessionState
- {
- public void ProcessRequest(HttpContext context)
- {
- // 獲取要請(qǐng)求的文件名
- string filePath = context.Server.MapPath(context.Request.FilePath);
- // 用Fiddler查看響應(yīng)頭,如果看到有這個(gè)頭,就表示是由這段代碼處理的。
- context.Response.AddHeader("SeesionExist", (context.Session != null ? "yes": "no"));
- // 在這里,你可以訪問(wèn)context.Session
- // 設(shè)置響應(yīng)內(nèi)容標(biāo)頭
- context.Response.ContentType = "text/html";
- // 輸出文件內(nèi)容
- context.Response.TransmitFile(filePath);
- }
在這個(gè)案例中我為什么要選擇HttpHandler呢?
1. 我需要 響應(yīng)一類請(qǐng)求(所有的HTML文件)。
2. 請(qǐng)求要求支持 Session
所以我最終選擇了HttpHandler 。
Q:我需要壓縮所有的ASP.NET請(qǐng)求的響應(yīng)結(jié)果,該怎么實(shí)現(xiàn)?
想好了就來(lái)看看我的解決方案:
- /// <summary>
- /// 為請(qǐng)求支持gzip壓縮輸出的DEMO
- /// </summary>
- public class GzipModule : IHttpModule
- {
- public void Init(HttpApplication app)
- {
- app.BeginRequest += new EventHandler(app_BeginRequest);
- }
- void app_BeginRequest(object sender, EventArgs e)
- {
- HttpApplication app = (HttpApplication)sender;
- // 判斷瀏覽器是否支持GZIP壓縮
- string flag = app.Request.Headers["Accept-Encoding"];
- if( string.IsNullOrEmpty(flag) == false && flag.ToLower().IndexOf("gzip") >= 0 ) {
- // 設(shè)置GZIP壓縮
- app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress);
- app.Response.AppendHeader("Content-Encoding", "gzip");
- }
- }
在這個(gè)案例中我為什么要選擇HttpModule呢?
原因只有一個(gè):我需要 設(shè)置所有請(qǐng)求。
所以我最終選擇了HttpModule 。
#p#
如何選擇?
在結(jié)束這篇博客之前,再問(wèn)問(wèn)各位讀者:現(xiàn)在知道何時(shí)選擇HttpHandler還是HttpModule了嗎?
如果還沒(méi)有看明白,那我就最后告訴你一個(gè)識(shí)別方法:
1. 如果要響應(yīng)一類請(qǐng)求,那么就選擇HttpHandler。
2. 如果要修改或者檢查所有請(qǐng)求(總之就是不生成響應(yīng)結(jié)果),那就選擇HttpModule。
最后給各位留下一個(gè)題目,下面這些ASP.NET提供的功能,它們是采用了哪個(gè)方式實(shí)現(xiàn)的?
1. Session
2. 身份認(rèn)證
3. URL授權(quán)檢查
4. 通過(guò)trace.axd查看跟蹤信息
5. OutputCache
6. 禁止下載config文件
7. 禁止查看下載源代碼文件
注意:本文的主題是:選擇HttpHandler還是HttpModule,所以請(qǐng)不要扯遠(yuǎn)了。
原文鏈接:http://www.cnblogs.com/fish-li/archive/2013/01/04/2844908.html