选择HttpHandler还是HttpModule?

开发 后端
我原以为在博客 用Asp.net写自己的服务框架 中做了那么多的演示应该把它们的使用方法说清楚了,然而有些人看了我的那些示例,仍然不知道该如何选择它们,为了实现同一个目标,我既用了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文档:

  1. 在处理该请求时将由 HttpApplication 类执行以下事件。 希望扩展 HttpApplication 类的开发人员尤其需要注意这些事件。  
  2. 1. 对请求进行验证,将检查浏览器发送的信息,并确定其是否包含潜在恶意标记。 有关更多信息,请参见 ValidateRequest 和脚本侵入概述。  
  3. 2. 如果已在 Web.config 文件的 UrlMappingsSection 节中配置了任何 URL,则执行 URL 映射。  
  4. 3. 引发 BeginRequest 事件。  
  5. 4. 引发 AuthenticateRequest 事件。  
  6. 5. 引发 PostAuthenticateRequest 事件。  
  7. 6. 引发 AuthorizeRequest 事件。  
  8. 7. 引发 PostAuthorizeRequest 事件。  
  9. 8. 引发 ResolveRequestCache 事件。  
  10. 9. 引发 PostResolveRequestCache 事件。  
  11. 10. 根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理。 如果该请求针对从 Page 类派生的对象(页),并且需要对该页进行编译,则 ASP.NET 会在创建该页的实例之前对其进行编译。  
  12. 11. 引发 PostMapRequestHandler 事件。  
  13. 12. 引发 AcquireRequestState 事件。  
  14. 13. 引发 PostAcquireRequestState 事件。  
  15. 14. 引发 PreRequestHandlerExecute 事件。  
  16. 15. 为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 IHttpAsyncHandler.BeginProcessRequest)。 例如,如果该请求针对某页,则当前的页实例将处理该请求。   
  17. 16. 引发 PostRequestHandlerExecute 事件。  
  18. 17. 引发 ReleaseRequestState 事件。  
  19. 18. 引发 PostReleaseRequestState 事件。  
  20. 19. 如果定义了 Filter 属性,则执行响应筛选。  
  21. 20. 引发 UpdateRequestCache 事件。  
  22. 21. 引发 PostUpdateRequestCache 事件。  
  23. 22. 引发 EndRequest 事件。  
  24. 23. 引发 PreSendRequestHeaders 事件。  
  25. 24. 引发 PreSendRequestContent 事件。  

图片中还反映了ASP.NET的三种主要的参与者:

1. HttpModule;2. HttpHandlerFactory;3. HttpHandler

有没有有想过:这三种参与者中,每种有多少个参与对象呢?

为了清楚地回答这个问题,我准备了下面的表格:

为什么要引入HttpHandlerFactory呢? 请看我的博客 细说 HttpHandler 的映射过程,今天就不重复这块内容了。

除开HttpHandlerFactory,我们可以发现:在ASP.NET管线中,HttpHandler应该只有一个,而HttpModule是可选的。

进而,我们是不是可以这样理解:HttpHandler才是处理请求的主角(不可缺少),HttpModule是配角(可以没有)?

#p#

理解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是个非常重要的类型,它的许多功能都属于框架的基础部分,不需要我们调用,因此,我们平时不会用到它。

我不想让博客走题,下面来看看今天的主角吧。

#p#

理解HttpHandler

前面说到HttpRuntime会将请求交给HttpApplication来处理,此时你有没有想过这样一个问题:为什么HttpApplication不直接处理请求,而是要再交给一个HttpHandler对象来处理呢?

答案是:每个请求的内容可能并不相同,它们存在多样性,因此ASP.NET采用了抽象工厂模式来处理这些请求。 ASP.NET在web.config的架构中,允许我们指定某些请求映射到一个HttpHandlerFactory,例如:

  1. <!--适用于IIS6的配置-->  
  2. <system.web>  
  3.     <httpHandlers>  
  4.         <add path="*.cspx" verb="*" type="MyMVC.AjaxHandlerFactory, MyMVC" validate="true" />  
  5.         <add path="*.aspx" verb="*" type="MyMVC.MvcPageHandlerFactory, MyMVC" validate="true" />  
  6.         <add path="/mvc/*" verb="*" type="MyMVC.MvcPageHandlerFactory, MyMVC" validate="true" />  
  7.     </httpHandlers>  
  8. </system.web>  
  9.  
  10. <!--适用于IIS7的配置(集成模式)-->  
  11. <system.webServer>  
  12.     <handlers>  
  13.         <add name="AjaxHandlerFactory" verb="*" path="*.cspx" type="MyMVC.AjaxHandlerFactory, MyMVC" preCondition="integratedMode" />  
  14.         <add name="MvcPageHandlerFactory" verb="*" path="*.aspx" type="MyMVC.MvcPageHandlerFactory, MyMVC" preCondition="integratedMode" />  
  15.         <add name="MvcPageHandlerFactory2" verb="*" path="/mvc/*" type="MyMVC.MvcPageHandlerFactory, MyMVC" preCondition="integratedMode" />  
  16.     </handlers>  
  17. </system.webServer>  

当某个请求与一个规则匹配后,ASP.NET会调用匹配的HttpHandlerFactory的GetHandler方法来获取一个HttpHandler实例, 最后由一个HttpHandler实例来处理当前请求。

HttpApplication是如何将请求交给HttpHandler实例来处理的呢?

为了理解这个过程,我们要来看一下IHttpHandler接口的定义:

  1. // 这个接口用于同步调用  
  2. // 异步版本的接口用法请参考:http://www.cnblogs.com/fish-li/archive/2011/11/20/2256385.html  
  3.  
  4. public interface IHttpHandler  
  5. {  
  6.     // 获取一个值,该值指示其他请求是否可以使用 IHttpHandler 实例。  
  7.     bool IsReusable { get; }  
  8.  
  9.     // 通过实现 IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。  
  10.     void ProcessRequest(HttpContext context);  
  11. }  

HttpApplication在将某个请求交给HttpHandler实例来处理时,是通过接口来调用的(ProcessRequest方法)。

与HttpHandler的相关话题:

1. 异步 HttpHandler:细说ASP.NET的各种异步操作

2. 如何重用HttpHandler:细说 HttpHandler 的映射过程

HttpHanlder的典型应用

  1. <%@ WebHandler Language="C#" Class="Login" %>  
  2. using System;  
  3. using System.Web;  
  4. public class Login : IHttpHandler {        
  5.     public void ProcessRequest (HttpContext context) {  
  6.         context.Response.ContentType = "text/plain";  
  7.  
  8.         string username = context.Request.Form["name"];  
  9.         string password = context.Request.Form["password"];  
  10.  
  11.         if( password == "aaaa" ) {  
  12.             System.Web.Security.FormsAuthentication.SetAuthCookie(username, false);  
  13.             context.Response.Write("OK");  
  14.         }  
  15.         else {  
  16.             context.Response.Write("用户名或密码不正确。");  
  17.         }  
  18.     }  

通常我去这样创建一个ashx文件(HttpHanlder)响应某种特殊的请求。

所以,我们应该这样理解HttpHanlder:一个HttpHanlder用于响应一类特定的请求。

我们经常用到的HttpHanlder有哪些?

  1. 1. aspx页面。  
  2. 2. asmx服务文件。  
  3. 3. ashx文件(一般处理程序)。  
  4. 4. 实现IHttpHandler接口的自定义类型。  

我们通常使用HttpHanlder做什么?

 

注意观察表格中实现目标,我们可以得到一个结论:使用HttpHanlder的目的是生成响应结果。

#p#

理解HttpModule

设计HttpHanlder的目的很明确:生成响应结果。

那么,设计HttpModule又是为什么呢?

前面说过,一个HttpHanlder用于处理一类特定的请求,每个aspx, ashx都可以认为是一类请求。有时候我们发现所有页面可能都需要某些相同的检查功能(如身份检查),假如只能使用HttpHanlder,那我们就要让所有页面都去调用那些相同的检查功能。谁愿意做这些重复的事情?或许有些人会回答,可以自己实现一个基类,把检查功能放在基类中去调用。然而,这种做法只能解决重复调用问题,它会让代码失去灵活性(扩展性),试想一下:如果需要再加入新的检查功能,或者用新的检查方法替换原有的检查逻辑时怎么办?只能是修改基类了吧?

设计HttpModule的目的正是为了提供一个灵活的方法解决这种功能重用问题。它采用事件(观察者)的设计模式,将某些HttpHanlder都需要的功能抽取出来,形成不同的观察者类型,这些观察者类型可以编译成类库形式,供多个网站项目共用。为了让ASP.NET管线更灵活,ASP.NET允许我们在web.config中自由配置需要的HttpModule,例如:

  1. <!--适用于IIS6的配置-->  
  2. <system.web>  
  3.     <httpModules>  
  4.         <add name="SetOutputCacheModule" type="MyMVC.SetOutputCacheModule, MyMVC"/>  
  5.     </httpModules>  
  6. </system.web>  
  7.  
  8. <!--适用于IIS7的配置(集成模式)-->  
  9. <system.webServer>  
  10.     <modules>  
  11.         <add name="SetOutputCacheModule" type="MyMVC.SetOutputCacheModule, MyMVC" preCondition="integratedMode" />  
  12.     </modules>  
  13. </system.webServer>  

配置只是告诉ASP.NET:这些HttpModule需要运行起来。有没有想过这些HttpModule到底是如何进入管线运行起来的呢?前面我只是说了HttpModule会订阅这些事件,那么事件又是在哪里订阅的呢?还是来看一下IHttpModule接口的定义吧:

  1. // 这个接口用于同步调用  
  2. // 异步用法请参考:http://www.cnblogs.com/fish-li/archive/2011/11/20/2256385.html  
  3.  
  4. public interface IHttpModule  
  5. {  
  6.     //  初始化模块,并使其为处理请求做好准备。  
  7.     void Init(HttpApplication app);  
  8.  
  9.     void Dispose();  
  10. }  

注意这个关键的Init方法,它传入一个HttpApplication类型的参数,有了HttpApplication对象,HttpModule就可以订阅HttpApplication的所有事件了。请看下面的示例代码:

  1. public class TestModule : IHttpModule  
  2. {  
  3.     public void Dispose() {}  
  4.  
  5.     public void Init(HttpApplication app)  
  6.     {  
  7.         app.PostAcquireRequestState += new EventHandler(app_PostAcquireRequestState);  
  8.         app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);  
  9.     }  
  10.     void app_PreRequestHandlerExecute(object sender, EventArgs e)  
  11.     {  
  12.         throw new NotImplementedException();  
  13.     }  
  14.     void app_PostAcquireRequestState(object sender, EventArgs e)  
  15.     {  
  16.         throw new NotImplementedException();  
  17.     }  
  18. }  

HttpModule的典型应用

  1. public class SetOutputCacheModule : IHttpModule  
  2. {  
  3.     public void Init(HttpApplication app)  
  4.     {  
  5.         app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);  
  6.     }  
  7.  
  8.     void app_PreRequestHandlerExecute(object sender, EventArgs e)  
  9.     {  
  10.         HttpApplication app = (HttpApplication)sender;  
  11.         Dictionary<string, OutputCacheSetting> settings = ConfigManager.Settings;  
  12.         if( settings == null )  
  13.             throw new ConfigurationErrorsException("SetOutputCacheModule加载配置文件失败。");  
  14.  
  15.         // 实现方法:  
  16.         // 查找配置参数,如果找到匹配的请求,就设置OutputCache  
  17.         OutputCacheSetting setting = null;  
  18.         if( settings.TryGetValue(app.Request.FilePath, out setting) ) {  
  19.             setting.SetResponseCache(app.Context);  
  20.         }  
  21.     }  

这个Module用于给一些在配置文件中指出要缓存的请求设置输出缓存,示例代码已在上篇博客 不修改代码就能优化ASP.NET网站性能的一些方法 介绍过了。其实设置输出缓存的最根本手段还是调用Response.Cache的一些公开方法,修改输出响应头。

我们用HttpModule做什么事情?

1. 修改某些请求(例如前面的示例修改了响应头)。

2. 检查检查请求(例如身份认证检查)。

HttpModule能处理哪些请求呢?

1. 默认是全部进入ASP.NET的请求。

2. 如果只需要处理部分请求,那么请自行判断(参考前面的示例)。

#p#

三大对象的总结

前面我分别介绍了HttpApplication,HttpHanlder和HttpModule,这里再把三者的关系重新梳理一遍。

在请求的处理过程中,HttpApplication对象主要扮演着控制管线处理流程的作用,它负责推进整个处理流程,除了在不同阶段引发不同的事件外(供HttpModule使用),HttpApplication对象还会根据当前请求寻找一个合适的IHttpApplicationFactory实例,并最终得到一个IHttpHandler的实例用于处理请求。

设计这三种类型的目的在于:

1. HttpApplication控制处理流程,在不同阶段引发不同的事件。

2. 由于请求的多样性,每个请求会由一个HttpHandler对象来处理。

3. 对于一些通用性的功能,尤其是与响应内容无关的,设计成HttpModule是最合适的。

案例演示

Q:我有一些html文件,需要做身份认证检查(判断Session),我该如何实现?

想好了就来看看我的解决方案:

  1. /// <summary>  
  2. /// 用于响应HTML文件的处理器  
  3. /// </summary>  
  4. public class HtmlHttpHandler : IHttpHandler, IRequiresSessionState, IReadOnlySessionState   
  5. {  
  6.     public void ProcessRequest(HttpContext context)  
  7.     {  
  8.         // 获取要请求的文件名  
  9.         string filePath = context.Server.MapPath(context.Request.FilePath);  
  10.  
  11.         // 用Fiddler查看响应头,如果看到有这个头,就表示是由这段代码处理的。  
  12.         context.Response.AddHeader("SeesionExist", (context.Session != null ? "yes""no"));  
  13.  
  14.         // 在这里,你可以访问context.Session  
  15.  
  16.         // 设置响应内容标头  
  17.         context.Response.ContentType = "text/html";  
  18.  
  19.         // 输出文件内容  
  20.         context.Response.TransmitFile(filePath);  
  21.     }  

在这个案例中我为什么要选择HttpHandler呢?

1. 我需要 响应一类请求(所有的HTML文件)。

2. 请求要求支持 Session

所以我最终选择了HttpHandler 。

Q:我需要压缩所有的ASP.NET请求的响应结果,该怎么实现?

想好了就来看看我的解决方案:

  1. /// <summary>  
  2. /// 为请求支持gzip压缩输出的DEMO  
  3. /// </summary>  
  4. public class GzipModule : IHttpModule  
  5. {  
  6.     public void Init(HttpApplication app)  
  7.     {  
  8.         app.BeginRequest += new EventHandler(app_BeginRequest);  
  9.     }  
  10.  
  11.     void app_BeginRequest(object sender, EventArgs e)  
  12.     {  
  13.         HttpApplication app = (HttpApplication)sender;  
  14.  
  15.         // 判断浏览器是否支持GZIP压缩  
  16.         string flag = app.Request.Headers["Accept-Encoding"];  
  17.         ifstring.IsNullOrEmpty(flag) == false && flag.ToLower().IndexOf("gzip") >= 0 ) {  
  18.             // 设置GZIP压缩  
  19.             app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress);  
  20.             app.Response.AppendHeader("Content-Encoding""gzip");  
  21.         }  
  22.     }  

在这个案例中我为什么要选择HttpModule呢?

原因只有一个:我需要 设置所有请求。

所以我最终选择了HttpModule 。

#p#

如何选择?

在结束这篇博客之前,再问问各位读者:现在知道何时选择HttpHandler还是HttpModule了吗?

如果还没有看明白,那我就最后告诉你一个识别方法:

1. 如果要响应一类请求,那么就选择HttpHandler。

2. 如果要修改或者检查所有请求(总之就是不生成响应结果),那就选择HttpModule。

最后给各位留下一个题目,下面这些ASP.NET提供的功能,它们是采用了哪个方式实现的?

1. Session

2. 身份认证

3. URL授权检查

4. 通过trace.axd查看跟踪信息

5. OutputCache

6. 禁止下载config文件

7. 禁止查看下载源代码文件

注意:本文的主题是:选择HttpHandler还是HttpModule,所以请不要扯远了。

原文链接:http://www.cnblogs.com/fish-li/archive/2013/01/04/2844908.html

责任编辑:张伟 来源: 博客园
相关推荐

2013-03-20 10:43:15

HttpHandlerHttpModule

2009-07-20 16:12:21

ASP.NET Fra

2013-01-15 10:50:42

2009-07-27 15:48:43

2013-05-30 13:30:00

代码效率程序员

2013-07-04 14:54:24

Android

2023-11-09 09:13:48

GraphQLAPI 架构

2009-06-19 13:31:39

GETPOSTAjax

2009-08-10 14:55:43

ASP.NET htt

2012-08-16 09:38:38

ASP.NET

2020-07-01 14:05:21

麦肯锡机器人从业者

2013-06-08 09:05:06

2019-07-05 15:45:39

UbuntuFedoraLinux

2015-08-19 10:13:53

DaasVDI

2011-04-13 16:49:26

HTTPASP.NET

2011-04-13 16:59:41

HTTPASP.NET

2011-04-13 16:35:47

HTTPASP.NET

2019-07-05 10:53:55

ReactVue前端

2012-06-14 10:06:43

创业动态语言静态语言

2009-07-02 09:25:15

选择Hibernate
点赞
收藏

51CTO技术栈公众号