8.2 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 ”属性来访问这两个对象。例如:
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
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 对象之外,还完成了另一个很重要的工作——向 H ttpApplicationFactory 类的一个实例 [3] 申请 分配一个 HttpApplication 对象用于管理整个 HTTP 请求处理管线中的各种事件。
H ttpApplicationFactory 对象负责管理 一个 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 )。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 414.75pt; HEIGHT: 222.75pt" o:ole="" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/Users/JINXUL~1/AppData/Local/Temp/msohtmlclip1/01/clip_image001.emz"></imagedata></shape>
如 图 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.NE T 应用程序事件”),特定的 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技术书籍都有,就不再赘述了。
本文如有错误及疏漏之处,也恳求高手指出。
祝大家学习顺利。
金旭亮
2008年国庆于北京