ASP.NET管线与应用程序生命周期
8.1节介绍了IIS的系统架构和HTTP请求处理的总体流程,从中可以知道每个ASP.NET网站都对应着一个Web应用程序,此Web应用程序可以响应HTTP请求,为用户提供所需的信息。那么,ASP.NET应用程序具体是如何响应HTTP请求的?包括哪些具体的处理流程?这涉及到ASP.NET应用程序的生命周期问题。
8.2.1 ASP.NET应用程序生命周期*
本节以IIS 6为例分步介绍ASP.NET应用程序处理HTTP请求的处理流程。IIS 7的处理过程与IIS 6相比有些小变化,但总体上是一致的。
1 浏览器发出访问某ASP.NET网页的HTTP请求
假设这个请求是针对此网页所属的ASP.NET应用程序的第一次请求。
当此请求到达Web服务器时,由HTTP.SYS负责接收,根据此请求的URL,HTTP.SYS将其传递给此ASP.NET应用程序所对应的应用程序池,由在此应用程序池中运行的工作者进程负责处理请求[1]。
工作者进程接收到这个请求之后,装载专用于处理ASP.NET页面的一个ISAPI扩展“aspnet_isapi.dll”,并将HTTP请求传给它。
工作者进程加载完aspnet_isapi.dll后,由aspnet_isapi.dll负责加载ASP.NET应用程序的运行环境――CLR[2]。
工作者进程工作于非托管环境(指Windows操作系统本身)之中,而.NET中的对象则工作于托管环境(指CLR)之中,aspnet_isapi.dll起到了一个沟通两者的桥梁作用,将收到的HTTP请求(由非托管环境传来)转发给相应.NET对象(处于托管环境中)处理。
2 创建ApplicationManager对象和应用程序域
加载CLR之后,由ApplicationManager类负责创建一个应用程序域。每个ASP.NET应用程序都运行于自己的应用程序域中,由唯一的应用程序标识符标识。
每个应用程序域都对应着一个ApplicationManager类的实例,由它来负责管理运行在域中的ASP.NET应用程序(比如启动和停止一个ASP.NET应用程序,在指定的ASP.NET应用程序中创建对象等等)。
3 创建HostingEnvironment对象
在为ASP.NET应用程序创建应用程序域的同时,会创建一个HostingEnvironment对象,此对象提供了ASP.NET应用程序的一些管理信息(比如ASP.NET应用程序的标识,对应的虚拟目录和物理目录),并提供了一些附加的功能(比如在应用程序域中注册一个对象,模拟特定的用户等等)。
4为每个请求创建 ASP.NET 核心对象
当应用程序域创建完成之后,一个ISAPIRuntime对象被创建,并自动调用它的ProcessRequest()方法。在此方法中,ISAPIRuntime对象根据传入的HTTP请求创建一个HttpWorkerRequest对象,此对象以面向对象的方式包装了HTTP请求的各种信息(这就是说,原始的HTTP请求信息被封装为HttpWorkerRequest对象)。然后,调用ISAPIRuntime对象的StartProcessing()方法启动整个HTTP请求处理过程(此即“HTTP管线:HTTP Pipeline”),在这个处理过程的开端,一个HttpRuntime类型的对象被创建,前面创建好的HttpWorkerRequest对象作为方法参数被传送给此HttpRuntime对象的ProcessRequest()方法。
在HttpRuntime类的ProcessRequest()方法中完成了一些非常重要的工作,其中与Web软件工程师关系最紧密的是:
HttpRuntime类的ProcessRequest()方法根据HttpWorkerRequest对象中所提供的HTTP请求信息,创建了一个HttpContext对象。
HttpContext对象之所以重要,是因为此对象包容了另两个在ASP.NET编程中非常常见的对象:HttpResponse和HttpRequest。
HttpRequest对象中的信息来自于原始的HTTP请求,比如它的Url属性就代表了原始HTTP请求信息中的URL。
而HttpResponse对象则拥有一些属性和方法,用于生成要返回给浏览器的信息。
Page类提供了相应的属性来引用这两个对象,因此在ASP.NET网页中可以直接使用“Requset”和“Response”属性来访问这两个对象。例如:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(Request.Url);
}
}
Page类的Context属性引用HttpContext对象,因此,上述代码也可以改写为以下形式:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Context.Response.Write(this.Context.Request.Url);
}
}
关于HttpContext、HttpResponse和HttpRequest这三个对象,必须掌握以下的要点:
l HttpContext对象包容HttpResponse和HttpRequest这两个对象,可以从HttpRequest对象获取HTTP请求的相关信息,而要向浏览器输出的内容可以通过调用HttpResponse的方法实现。
l 针对每个HTTP请求,ASP.NET都会创建一个HttpContext对象,在整个HTTP处理过程中,此对象都是可以访问的。
5 分配一个HttpApplication 对象用于处理请求
HttpRuntime类的ProcessRequest()方法除了创建HttpContext对象之外,还完成了另一个很重要的工作——向HttpApplicationFactory类的一个实例[3]申请分配一个HttpApplication 对象用于管理整个HTTP请求处理管线中的各种事件。
HttpApplicationFactory对象负责管理一个HttpApplication对象池[4],当有HTTP请求到来时,如果池中还有可用的 HttpApplication对象,就直接分配此对象用于处理HTTP请求,否则,创建一个新的HttpApplication对象。
6 HttpApplication对象启动HTTP管线
HttpApplication对象负责装配出整个“HTTP请求处理管线(HTTP Pipeline)”,可以将“HTTP请求处理管线”与现代工厂中的“生产流水线”做个类比。前面步骤中创建好的HttpContext对象就是这个生产流水线要加工的“产品”,当它流经“生产流水线”的不同部分时,将被进行特定的加工和处理过程。
这些特定的“加工和处理过程”是怎样进行的?
简单地说,HttpContext对象经过“生产流水线”的不同部分时,HttpApplication对象会先后激发出一连串的事件[5]。一种特定的组件——HTTP模块(HTTP Module)可以响应这些事件,在此事件响应代码中可以对HttpContext对象进行“加工和处理”,从这个意义上说,HTTP模块可以看成是“生产流水线”中的工人。HTTP模块其实就是前面所介绍过的“ISAPI筛选器”。
HTTP模块对象是在HttpApplication对象的InitModules()方法[6]中被创建的,我们一般在HTTP模块对象Init()方法[7]中书写代码使其可以响应HttpApplication对象所激发的特定事件。
ASP.NET提供了一些预定义的HTTP模块响应特定的事件,Web软件工程师也可以编写自己的HTTP模块并将其插入到“HTTP请求处理管线”中[8]。
在流水线的中部(处理完了相关的事件),HttpContext对象被最终的Page对象所接收(这就是为何可以在ASP.NET页面中通过Page类定义的Context属性访问HttpContext对象的原因)。
每个被访问的ASP.NET页面都会被转换为一个“派生自Page类的页面类”。
注意:Page类实现了IHttpHandler接口,此接口定义了一个ProcessRequest()方法。
ASP.NET页面类生成以后被自动编译为程序集,然后其ProcessRequest()方法被自动调用(因为Page类实现了IHttpHandler接口,所以肯定有此方法)。在此方法中,Web软件工程师编写的代码被执行(如果有的话)。ProcessRequest()方法的执行结果再次被HttpContext对象所承载,控制又转回到“HTTP请求处理流水线”中,HttpApplication对象继续激发后继的事件。这时,如果还有特定的HTTP模块响应这些事件,则它们会被自动调用。
HttpContext对象带着最后的处理结果来到了“HTTP请求处理管线”的未端,其信息被取出来,再次以aspnet_isapi.dll为桥梁传送给工作者进程。工作者进程再将HTTP请求的处理结果转给HTTP.SYS,由它负责将结果返回给浏览器。
根据前面的介绍,可以将整个Http管线分成三段:预处理阶段à处理阶段à后处理阶段(图8‑14)。
图 8‑14 HTTP管线的三个阶段
如图 8‑14所示,HTTP管线的预处理和后处理阶段主要由多个HTTP模块参与,通过事件来驱动,这两个阶段完成的工作主要是对HttpContext对象的各种属性进行修改。
对HTTP请求的处理过程最终是由一个实现IHttpHandler接口的对象在“处理阶段”完成的。每一个ASP.NET网页生成的页面类都实现了此接口。创建出合适的HTTP请求处理对象的工作由PageHandlerFactory对象[9]负责完成。
由此可见,实现了IHttpHandler接口的对象负责处理HTTP请求,这就是它被称为“Handler(处理程序)”的原因。
除了最常见的ASP.NET网页之外,Web软件工程师还可以创建自己的实现了IHttpHandler接口的对象,并将其插入到HTTP管线中用于处理HTTP请求。
当HTTP请求处理完毕,相关的对象被释放,但创建的应用程序域,以及HttpApplication等对象仍然存活,以响应下一次HTTP请求。
7 ASP.NET应用程序生命周期小结
本节中介绍了ASP.NET应用程序的生命周期,这是一个相当复杂的过程,也许用以下通俗的类比更容易理解:
l “HTTP请求处理管线”就是一条现代工厂中的“生产流水线”,HttpContext对象就是这条流水线上要加工的产品。
l HttpHandler(HTTP处理程序)对象是整个“产品生产线”的核心,由它负责将产品装配成形。
l HttpModule(HTTP模块)相当于“生产线”上的辅助工人,他们对产品(HttpContext对象)进行“预处理”(为装配产品作准备)和“后处理”(为产品出厂作准备,比如贴商标)。
l HttpApplication对象是整个“生产线”的“领导” ,他负责给“生产线”分配工人(初始化并装载所有注册的HttpModule),然后会激发一系列的事件(被称为“ASP.NET应用程序事件”),特定的HttpModule负责响应特定的事件。
[1] 如果工作者进程不存在,则IIS监控程序WAS会创建一个,否则,复用已有的工作者进程。
[2] IIS 7集成模式下,由于CLR是预加载的,所以这一步就不需要了。
[3] “类的实例”与“类的对象”含义等同,都是指以类为模板创建出来的对象。
[4] 对象池(object pool)是面向对象软件系统常见的一种对象组织方式,可以将其看成是一个对象容器。对象池中放有事先创建好的多个对象。当外界需要某个对象时,可以直接从池中取出一个现成的对象使用,这就避免了频繁创建对象所带来的性能损失。
[5] HttpApplication定义了相当多的事件,完整的事件清单请查看MSDN。
[6] 此方法会在获取HttpApplication对象时被自动调用。
[7] 所有HTTP模块都要实现IHttpModule接口,Init()方法由此接口所定义。
[8] 通过在Web.Config中插入特定的内容可以将自定义的HTTP模块加入到HTTP请求的处理流程中。
[9] 这是ASP.NET技术框架中的另一个核心类。
*******************************************
本系列文章结束语:
理解Http PipeLine在ASP.NET编程中有着重要的意义,只有对它有所了解,才能理解开发中遇到的种种问题,并为学习和掌握更复杂的Web开发技术(比如自定义HttpModule和HttpHandler)打下基础。
到此为止,有关ASP.NET Web编程原理的系列文章就贴完了。之所以只贴这部分,是因为我发现许多ASP.NET技术书籍对这一部分内容都语焉不详,一带而过,而这一部分又是非常重要的,期望这四篇文章能对大家有所帮助。其他的常规内容绝大多数ASP.NET技术书籍都有,就不再赘述了。
本文如有错误及疏漏之处,也恳求高手指出。
祝大家学习顺利。