Asp.net Process Model 之三:HttpModule 和 HttpHandler
HttpApplication是整个ASP.NET基础架构的核心,它负责处理分发给它的HTTP请求。HttpApplication处理请求的整个生命周期是一个相对复杂的过程,在该过程的不同阶段会触发相应的事件。我们可以注册相应的事件,将我们的处理逻辑注入到HttpApplication处理请求的某个阶段。
对于一个ASP.NET应用来说,HttpApplication派生于global.asax文件,我们可以通过创建global.asax文件对HttpApplication的请求处理行为进行定制。global.asax采用一种很直接的方式实现了这样的功能,这种方式既不是我们常用的方法重写(Method Overriding)或者事件注册,而是直接采用方法名匹配。在global.asax中,我们按照这样的方法命名规则进行事件注册:Application_{Event Name}。比如Application_BeginRequest方法用于处理HttpApplication的BeginRequest事件。如果通过VS创建一个global.asax文件,下面是默认的定义。
<script runat="server">
void Application_Start(object sender, EventArgs e)
{}
void Application_End(object sender, EventArgs e)
{}
void Application_Error(object sender, EventArgs e)
{}
void Session_Start(object sender, EventArgs e)
{}
void Session_End(object sender, EventArgs e)
{}
</script>
但是这种方式在很多情况下却达不到我们的要求,更多地,我们需要的是一种Plug-in的实现方式:我们在外部定义一些Request Processing的功能,需要直接运用到我们的Application之中。通过使用HttpModule封装这些功能模块,并将其注册到我们的Application的发式可以很简单的实现这种功能。
一、HttpModule
HttpModule之于Asp.net,就像ISAPI filter之于IIS,它并不对请求进行响应,但可以在请求送达响应程序前截获请求,对请求进行附加操作,如添加消息、重定向url、身份验证等,也可以在响应传送到ISAPI之前截获响应,对响应结果进行附加操作。
HttpModule实现了System.Web.IHttpModule 接口,该接口很简单,仅有两个成员:
public interface IHttpModule
{
// Methods
void Dispose();
void Init(HttpApplication context);
}
我们只要在Init方法中注册相应的HttpApplication Event Handler就可以了,可以对第二篇Asp.net Runtime Pipeline中提到的HttpApplication的所有事件定制消息处理函数,当特定事件发生时,HttpApplication会自动调用注册的消息处理函数。
可注册的事件
BeginRequest:在Asp.net响应请求时作为Http执行管线链中的第一个事件发生
AuthenticateRquest:在安全模块已建立用户标识时发生。AuthenticateRequest 事件发出信号表示配置的身份验证机制已对当前请求进行了身份验证。预订 AuthenticateRequest 事件可确保在处理附加的模块或事件处理程序之前对请求进行身份验证。
PostAuthenticateRquest:在安全模块已建立用户标识时发生。PostAuthenticateRequest 事件在 AuthenticateRequest 事件发生之后引发。预订 PostAuthenticateRequest 事件的功能可以访问由 PostAuthenticateRequest 处理的任何数据。
AuthorizeRequest:在安全模块已验证用户授权时发生。AuthorizeRequest 事件发出信号表示 ASP.NET 已对当前请求进行了授权。预订 AuthorizeRequest 事件可确保在处理附加的模块或事件处理程序之前对请求进行身份验证和授权。
PostAuthorizeRequest:在安全模块已验证用户授权时发生。PostAuthorizeRequest 事件发出信号表示 ASP.NET 已对当前请求进行了授权。预订 PostAuthorizeRequest 事件可确保在处理附加的模块或处理程序之前对请求进行身份验证和授权。
ResolveRequestCache:当ASP.NET 完成授权事件以使缓存模块为来自缓存的请求服务时发生,从而跳过事件处理程序(例如某个页或 XML Web services)的执行。
PostResolveRequestCache:在 ASP.NET 跳过当前事件处理程序的执行并允许缓存模块满足来自缓存的请求时发生。
PostMapRequestHandler:在 ASP.NET 已将当前请求映射到相应的事件处理程序时发生。
AcquireRequestState:当 ASP.NET 获取与当前请求关联的当前状态(如会话状态)时发生。AcquireRequestState 事件在创建了事件处理程序之后引发。
PostAcquireRequestState:在已获得与当前请求关联的请求状态(例如会话状态)时发生。
PreRequestHandlerExecute :在 ASP.NET 开始执行事件处理程序(例如,某页或某个 XML Web service)前发生。
PostRequestHandlerExecute:在 ASP.NET 事件处理程序(例如,某页或某个 XML Web service)执行完毕时发生。
ReleaseRequestState:在 ASP.NET 执行完所有请求事件处理程序后发生。该事件将使状态模块保存当前状态数据。
PostReleaseRequestState:在 ASP.NET 已完成所有请求事件处理程序的执行并且请求状态数据已存储时发生。
UpdateRequestCache:当 ASP.NET 执行完事件处理程序,使缓存模块存储响应时发生,这些响应将用于为后续来自缓存的请求提供服务。
PostUpdateRequestCache:在 ASP.NET 完成了缓存模块的更新并存储了响应时发生,这些响应用于满足来自缓存的后续请求。
LogRequest:Asp.net为当前请求记录日志前发生。该事件在IIS 7.0集成模式下才有效,且要求有.Net Framework 3.0
PostLogRequest:当Asp.net为所有的HttpHandler事件处理程序完成LogRequest事件时发生。该事件在IIS 7.0集成模式下才有效,且要求有.Net Framework 3.0
EndRequest:在 ASP.NET 响应请求时作为 HTTP 执行管线链中的最后一个事件发生。
PreSendRequestHeaders:恰好在 ASP.NET 向客户端发送 HTTP 标头之前发生。
PreSendRequestContent:恰好在 ASP.NET 向客户端发送内容之前发生
定制HttpModule
{
public void Init(HttpApplication application)
{
application.AuthenticateRequest +=
new EventHandler(this.OnAuthenticateRequest);
}
public void Dispose() { }
public void OnAuthenticateRequest(object source, EventArgs eventArgs)
{
}
}
定义完定制的HttpModule之后,需要在配置文件中这册该Module,注册位置时web.config文件的httpModules配置段:
<httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<add name="CustomModule" type="BasicAuthCustomModule" />
</httpModules>
定义好定制的HttpModule并注册之后,HttpApplication会自动读取配置文件,并调用HttpModule的Init方法进行事件注册。在此,重新回顾下上节提到的HttpApplication实例创建时调用的InitModules函数,看看定制Module是如何起作用的
private void InitModules()
{
this._moduleCollection = RuntimeConfig.GetAppConfig().HttpModules.CreateModules();
this.InitModulesCommon();
}
private void InitModulesCommon()
{
int count = this._moduleCollection.Count;
for (int i = 0; i < count; i++)
{
this._currentModuleCollectionKey = this._moduleCollection.GetKey(i);
this._moduleCollection[i].Init(this);
}
this._currentModuleCollectionKey = null;
this.InitAppLevelCulture();
}
到此为止,就完成了Asp.net的HttpModule扩展功能。
HttpModule事件流程
一个HTTP请求在HttpModule容器的传递过程中,会在某一时刻(ResolveRequestCache事件发生后)将这个HTTP请求传递给HttpHandler容器。在这个事件之后,HttpModule容器会建立一个HttpHandler的入口实例,但是此时并没有将HTTP请求控制权交出,而是继续触发AcquireRequestState事件以及PreRequestHandlerExcute事件。在PreRequestHandlerExcute事件之后,HttpModule窗口就会将控制权暂时交给HttpHandler容器,以便进行真正的HTTP请求处理工作。
而在HttpHandler容器内部会执行ProcessRequest方法来处理HTTP请求。在容器HttpHandler处理完毕整个HTTP请求之后,会将控制权交还给HttpModule,HttpModule则会继续对处理完毕的HTTP请求信息流进行层层的转交动作,直到返回到客户端为止。
页面执行效果如下:
多个自定义HttpModule的触发顺序
在web.config文件中引入自定义HttpModule的顺序就决定了多个自定义HttpModule在处理一个HTTP请求的接管顺序。注:系统默认那几个HttpModule是最先由ASP.NET Framework所加载上去的。
假定有Module1和Module2两个定制HttpModule,在web.config配置中Module1定义在Module2之前,则页面执行效果如下:
在HttpModule中终止此次的HTTP请求
可以利用HttpModule通过调用HttpApplication.CompleteRequest()方法实现当满足某一个条件时终止此次的HTTP请求。
需要注意的是,即使调用了HttpApplication.CompleteRequest()方法终止了一个HTTP请求,ASP.NET Framework仍然会触发HttpApplication后面的这3个事件:EndRequest事件、PreSendRequestHeaders事件、PreSendRequestContent事件。如果存在多个自定义的HttpModule的话,当Module1终止了一个HTTP请求,这个HTTP请求将不会再触发Module2中相应的事件了,但Module2的最后三个事件仍会被触发。
二、HttpHandler
HttpModule是针对所有request,即任何一个request到达后都会触发注册的事件处理函数。但是HttpHandler则是针对某一类请求的,如对页面Default.aspx的请求。
所有的HttpHandler都实现了接口IHttpHandler。下面是IHttpHandler的定义,方法ProcessRequest提供了处理请求的实现。
public interface IHttpHandler
{
void ProcessRequest(HttpContext context);
bool IsReusable { get; }
}
定制HttpHandler
可以定制HttpHandler已处理特殊类型的资源,或覆盖已有的资源处理方式.
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.IO;
public class MyHandler:IHttpHandler
{
public MyHandler()
{
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
string FileName = context.Server.MapPath(context.Request.FilePath);
if (File.Exists(FileName)){
context.Response.ContentType = "text/plain";
context.Response.WriteFile(FileName);
}else{
context.Response.ContentType = "image/JPEG";
context.Response.WriteFile("../Images/Winter.jpg");
}
}
}
定制好后需要在配置文件web.config中的httpHandlers配置段中注册:
<httpHandlers>
<add verb="*" path="*.foo" type="MyHandler" />
</httpHandlers>
当用户在客户端向服务器请求类型为foo的资源时,asp.net就会调用注册的处理事件MyHandler处理资源,即直接把资源内容送回客户端。
HttpHandlerFactory
对于某些HttpHandler,具有一个与之相关的HttpHandlerFactory,用于创建或者获取相应的HttpHandler。HttpHandlerFactory实现接口IHttpHandlerFactory,方法GetHandler用于创建新的HttpHandler,或者获取已经存在的HttpHandler。
public interface IHttpHandlerFactory
{
IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
void ReleaseHandler(IHttpHandler handler);
}
参数说明:
context :HttpContext 类的实例,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session 和 Server)的引用。
- requestType:客户端使用的 HTTP 数据传输方法(GET 或 POST)。
- url :所请求资源的 RawUrl。
- pathTranslated :所请求资源的 PhysicalApplicationPat
HttpHandlerFactory能够针对某类资源创建不同的HttpHandler,具有更高的灵活性和可用性。而且,有时Framework并不直接把请求送给HttoHandler,而是发送给HttpHandlerFactory,由Factory根据请求创建HttpHandler。
.net中内置的处理程序中就包含几个Factory:
<add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler" />
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory" />
<add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory" />
<add verb="*" path="*.asmx" type="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>
<add verb="*" path="*.rem" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
<add verb="*" path="*.soap" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
<add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.ascx" type="System.Web.HttpForbiddenHandler" />
<add verb="GET,HEAD" path="*.dll.config" type="System.Web.StaticFileHandler" />
<add verb="GET,HEAD" path="*.exe.config" type="System.Web.StaticFileHandler" />
<add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.cs" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.csproj" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.vb" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.vbproj" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.webinfo" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.asp" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.licx" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.resx" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.resources" type="System.Web.HttpForbiddenHandler" />
<add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler" />
<add verb="*" path="*" type="System.Web.HttpMethodNotAllowedHandler" />
</httpHandlers>
其中的PageHandlerFactory就是一个HttpHandlerFactory,其方法定义如下:
{
return this.GetHandlerHelper(context, requestType, VirtualPath.CreateNonRelative(virtualPath), path);
}
private IHttpHandler GetHandlerHelper(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath)
{
Page page = BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page), context, true, true) as Page;
if (page == null)
{
return null;
}
page.TemplateControlVirtualPath = virtualPath;
return page;
}
PageHandlerFactory根据虚拟路径创建不同的Page对象(实现了IHttpHandler)作为响应请求的Handler。
定制HttpHandlerFactory
{
using System;
using System.Web;
public class MyFactory : IHttpHandlerFactory
{
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public virtual IHttpHandler GetHandler(HttpContext context,
String requestType,
String url,
String pathTranslated)
{
String fname = url.Substring(url.LastIndexOf('/') + 1);
String cname = fname.Substring(0, fname.IndexOf('.'));
String className = "test." + cname;
Object h = null;
try
{
h = Activator.CreateInstance(Type.GetType(className));
}
catch (Exception e)
{
throw new HttpException("Factory couldn't create instance " +
"of type " + className, e);
}
return (IHttpHandler)h;
}
public virtual void ReleaseHandler(IHttpHandler handler)
{
}
}
public class abc : IHttpHandler
{
public virtual void ProcessRequest(HttpContext context)
{
context.Response.Write("<html><body>");
context.Response.Write("<p>ABC Handler</p>\n");
context.Response.Write("</body></html>");
}
public virtual bool IsReusable
{
get { return true; }
}
}
public class xyz : IHttpHandler
{
public virtual void ProcessRequest(HttpContext context)
{
context.Response.Write("<html><body>");
context.Response.Write("<p>XYZ Handler</p>\n");
context.Response.Write("</body></html>");
}
public virtual bool IsReusable
{
get { return true; }
}
}
}
这段代码定义了两个不同的HttpHandler,这两个hanlder有MyFactory创建。在配置文件中注册:
<httpHandlers>
<add verb="*" path="*.pxp" type="MyFactory" />
</httpHandlers>
当用户向服务器提交类型为pxp的资源时,asp.net并不是创建同样的实例来响应请求,而是根据资源名称的不同创建不同的Hanlder。abc Handler处理abc.pxp的请求,xyz Handler处理xyz.pxp的请求。
参考:
http://www.cnblogs.com/artech/archive/2009/06/20/1507165.html
http://www.cnblogs.com/stwyhm/archive/2006/08/09/471729.html