请求在ASP.NET的pipeline中经过的是怎样的处理呢?
==================
HttpApplication通过激发你应用程序中不同的事件来对请求的流动负责. HttpApplication.Init()方法建立并开始连续激活一系列的事件, 从而依次地调用并执行事件处理程序. 这些事件处理程序(event handlers)被关联到在global.asax中自动建立起来的事件上, 也被关联到任何已经附着的HttpModules上. HttpModule实质上是HttpApplication公布出来的事件的外部事件槽.
HttpModules和HttpHandlers二者都是被动态地通过读取Web.config文件中的项来加载的, 并且二者都被附着到事件链中.
HttpModule是真正的与具体的HttpApplication事件挂接在一起的事件处理程序, 而HttpHandler是一个被调用来handle应用程序级请求处理的终点.
Modules和Handlers二者都被作为HttpApplication.Init()方法调用的一部分来加载并附着在调用链上的. 下图展示了各种各样的事件, 还有他们何时发生并影响到了pipeline中的哪些部分.
图1
HttpContext, HttpModules, 和HttpHandlers有什么关系与区别?
=====================
HttpApplication自己对于发送给应用程序的数据一无所知, 它仅仅是一个通过事件与其他对象通信的信使一样的对象. 它触发事件, 并把HttpContext对象传递给调用的方法. 当前请求的实际的状态数据是存储在HttpContext对象中的. HttpContext提供所有请求相关的具体数据, 并从头到尾的跟随每一个请求走过整个pipeline. 下图2表现出了通过ASP.NET的pipeline的流程. 注意Context对象在请求的过程中从头到尾都是你的朋友, 帮助你在一个事件中存储信息, 并在稍后的事件中来读取这些信息.
一旦pipeline启动, HttpApplication就如同图1显示的那样, 一个接一个的触发事件. 只要事件与事件处理程序挂钩在一起了, 那么每一个这样的事件处理程序都会被触发并开始完成自己的任务. 这个过程的主要目的就是最终能调用到跟具体请求挂钩的HttpHandler. Handlers是ASP.NET请求处理的核心机制, 并且通常也是应用程序级代码被执行的地方. 记住, ASP.NET的页面还有Web Services framework都被实现为HttpHandler, 针对请求的所有核心处理都是在handler中完成的. Modules更趋向于是一种更核心的,用来预先加工传递给handler的Context, 或者迟后处理传递给handler的Context的一种类型. 在ASP.NET中典型的默认Modules(注意, 这里原文是hanlder, 博客园知识库中也是根据handler来翻译的, 不要被误导, 这里是英文版原作者的笔误. 图2中auth cache明显的都属于modules.)是Authentication, 预处理缓存, 还有各种在请求处理后的编码机制.
HttpModules
随着请求进入pipeline, 在HttpApplication中的许多事件被触发. 我们已经看到了在Global.asax中作为事件方法被公布的事件. 这种方式具体在应用程序上可能不会一直是你想要的样子. 如果你希望建立一般的HttpApplication事件的,能够被插入到任何的web应用程序中的钩子, 你可以使用HttpModules. HttpModules是可以重用的, 并不需要修改什么应用程序的代码, 只要在web.config中添加一个entry就可以了.
Modules在本质上是filters, 如同ISAPI Filter的功能, 但却是在ASP.NET请求的水平上的. Modules允许为每一个通过ASP.NET HttpApplication对象的请求勾住事件. 这些modules被存储在外部的程序集的类中, 由web.config配置, 在应用程序开始的时候被加载. 通过实践医学具体的借口和方法, Module被挂接到HttpApplication的事件链中. 多个modules可以勾在相同的事件上, 并且执行的顺序是由他们声明在web.config中的顺序决定的. 下面是一个Webconfig中的Module的定义的一个例子.
1: <configuration>
2: <system.web>
3: <httpModules>
4: <add name= "BasicAuthModule" type="HttpHandlers.BasicAuth,WebStore" />
5: </httpModules>
6: </system.web>
7: </configuration>
注意, 你需要指定完整的类型名和包括dll扩展名在内的程序集的名字.
Modules允许你查看任何一个进入的Web request, 并且根据触发的事件执行动作. modules对于修改request和response的内容是非常棒的, 通过modules的这种功能, 你可以提供自定义的认证方式, 或者提供对ASP.NET中的针对某个应用程序的请求的预处理或者后处理. 很多ASP.NET的特性比如认证和会话引擎都是以Http Modules的方式实现的.
Modules只能检查映射到ASP.NET的后缀名的请求, 比如说htm文件或图片文件的请求,一般就不会经过modules, 除非你在IIS里把它们也映射到ASP.NET上. 一个典型的将jpg文件映射到ASP.NET的用例是在请求某个特定文件夹下的图片时, 调用GDI+方法在图片上添加一些自定义的文字或图案, 就像盖章一样.
实现HttpModule非常简单: 你必须实现IHttpModule接口, 这个接口只包含两个方法Init() 和Dispose(). 传递给事件的参数包括一个对HttpApplication的引用, 通过application对象可以拿到context对象. 在这些方法里, 你可以勾住httpapplication的事件. 举个例子, 如果你想勾住AuthenticateRequest, 下面的代码可以参考一下:
1: public class BasicAuthCustomModule : IHttpModule
2: {
3: public void Init(HttpApplication application)
4: {
5: // *** Hook up any HttpApplication events
6: application.AuthenticateRequest +=
7: new EventHandler(this.OnAuthenticateRequest);
8: }
9:
10: public void Dispose() { }
11:
12: public void OnAuthenticateRequest(object source, EventArgs eventArgs)
13: {
14: HttpApplication app = (HttpApplication) source;
15: HttpContext Context = HttpContext.Current;
16: … do what you have to do… }
17: }
记住你的Module有权访问HttpContext对象, 从中你可以访问到所有其他ASP.NET pipeline的固有对象, 比方说Response 和Request, 所以你可以拿到input等信息. 但是记住, 这些东西可能在稍后的事件链中不可用.
你可以在Init方法中勾住很多时间, 所以你的一个Module可以管理不同的功能, 进行不同的操作. 很多情况下, 你需要勾住多个事件, 比如说log模块, 你需要在BeginRequest事件中记录开始时间, 在EndRequest事件中记录结束时间, 将它们记录在日志中.
当心一个关于HttpModules 和HttpApplication 的事件的重要陷阱: Response.End() 和HttpApplication.CompleteRequest()都会越过HttpApplication 和Module的事件链.
HttpHandlers
Modules是比较底层的, 每一个到ASP.NET应用程序的请求都会经过它的处理. 而HttpHandler是更加专注的, 它只操作那种针对一个具体的请求的映射, 比如说一个被映射到这个handler的页面的扩展名.
HttpHandler在他们的需求中的实现非常基本, 但是通过访问HttpContext对象, 还是可以做很多事情的. HttpHandlers们继承自一个非常简单的接口IHttpHandler(或者是他的异步版本IHttpAsyncHandler), 这个借口仅仅包含一个简单的方法ProcessRequest(), 还有一个简单的属性IsReusable. ProcessRequest是关键的方法. 它能得到传递给它的HttpContext对象的一个实例. 就这个方法, 从开始到结束地对一个web request负责.
仅仅一个, 又简单的方法? 是不是有点太简单了? 好吧, 简单的接口, 但是其中发生的事很不简单!
看看吧, WebForms和WebServices可是都被实现为HttpHandler的, 所以这个看起来简单的接口背后又不少强大的功能被包装起来了呢. 重要的是, 当请求达到HttpHandler时, 所有的ASP.NET的内部对象都已经被建立和配置好了, 等待着处理请求的开始. 提供从Web Server获取输入, 向Web Server发送输出这样的请求相关功能的HttpContext对象也是很重要的.
对于一个HttpHandler来说, 所有的动作都发生在这个简单的函数调用ProcessRequest()之中了, 它甚至可以如此简单:
1: public void ProcessRequest(HttpContext context)
2: {
3: context.Response.Write("Hello World");
4: }
也可以是能够通过HTML模板来渲染复杂的表单的WebForm页面引擎的完整实现那样的复杂. 关键在于你想要如何使用这个简单但是强大的接口!
因为Context对象已经对你是可用的了, 通过它你可以得到Request, Response, Session 和Cache对象. 现在你就可以使用所有的asp.net的关键特性来帮助你弄清楚客户输入的是什么, 你想返回给客户什么东西. 记住, Context对象在ASP.NET请求的整个生命期都是你的好朋友, 随时听后调用.
Handler关键的操作应该是最终将输出写入到Response对象中, 或者说的更清楚些: 写入到Response的OutputStream中. 这个输出是实际发给客户浏览器的数据. 幕后, ISAPIWorkerRequest负责将OutputStream发送回ISAPI的ecb.WriteClient方法, 那会执行产生IIS输出的动作.
图2
经典的描述:
ASP.NET pipeline中, request流动经过一系列的事件接口, 这些事件接口提供了很大的灵活性.
Application表现的像一个宿主容器, 它加载web application, 并随着request的到来而触发事件, 并带着request经过整个pipeline.
每一个请求的流动都要遵循一个普通的配置好的Http Modules的路径.
Modules检验每一个经过pipeline的请求, Handlers允许像WebForm和WebServices一样的应用程序的逻辑的实现.
为了给应用程序提供输入和输出, Context对象在整个请求处理的生命期内都存在, 通过它可以获得请求相关的具体信息.