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 ”属性来访问这两个对象。例如:
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.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 技术框架中的另一个核心