ASP.NET使用管道模型(PipleLines)处理HTTP请求
大多数人认为ASP.NET仅仅只是页面——使用模板来创建HTML页面然后返回给浏览器。但是这仅仅只是ASP.NET使用HTTP管道模型处理WEB程序很小的一方面。管道模型是类似于Web Services的一种在服务器端处理ASP.NET页面的框架技术。作为一名高级的ASP.NET的开发者,你必须清楚管道模型是如何工作的。这篇文章就是解释和阐述HTTP管道模型是如何处理HTTP请求的。
一、管道对象模型
在System.Web的命名空间中处理HTTP的请求主要使用管道模型。一般的管道模型的结构如图-1。在管道模型开始运行前,HTTP的请求首先被传到HttpRuntime类的一个实例中,然后这个HttpRuntime的对象开始检查请求并找出这个请求被发送到的那个应用程序(在管道程序看来,一个虚拟目录就是一个应用程序)。然后管道模型就使用一个HttpApplicationFactory对象来找出或者创建一个HttpApplication对象来处理这个请求,一个HttpApplication可以包含一系列HTTP module对象(派生自IHttpModule接口)。HTTP modules作为一个过滤器可以在HTTP请求和响应信息穿过管道模型时检查和修改这些信息的内容。然后HttpApplication对象就使用HTTP handler factory来找出或产生一个HTTP handler对象。HTTP handlers是HTTP通信的最后一步,它主要用于处理请求信息(request)和响应信息(response)。注:HTTP handlers和 handler factory分别派生自IHttpHandler接口和IHttpHandlerFactory接口。
图-1
一个HttpApplication包括它的modules、handler在同一时刻只能处理一个Request请求。如果多重request请求同时到达一个相同的application时,多重HttpApplication对象将会被使用。
管道模型使用一个HttpContext对象去描述声明每一个成对的request/response信息。这个对象在HttpApplicaiton和handler之间来回传递。每一个module也能访问当前的HttpContext。HttpContext对象通过属性来描述声明HTTP的request和response信息(分别创建HttpRequest类和HttpResponse类的对象);同样,HttpContext对象也能通过属性来描述声明安全信息和每一个call、session和application。图-2展示了部分HttpContext类常用的属性。
ASP.NET的HTTP管道模型是可扩展的,你可以实现自己的HTTP module、handler以及handler factory。你也可以直接继承HttpApplication类。
属性名
|
描述
|
Application
|
每一个application的request信息
|
Application Instance
|
正在处理request请求的Application对象
|
Cache
|
每一个application的缓存信息
|
Handler
|
正在处理request请求的Handler对象
|
Items
|
每一个request请求信息
|
Request
|
HTTP request 信息
|
Response
|
HTTP response 信息
|
Server
|
Utility functions
|
Session
|
Per-user cross-request state
|
User
|
User information
|
图-2:HttpContext类的常用属性
二、管道处理模型
ASP.NET的HTTP管道程序运行在IIS上来接收传递到进程中的request请求(被完整地传送到其他的web服务器上)。当IIS接收到一个HTTP的request请求时,它首先会通过目标URL检查文件的扩展名,如果文件的名被可执行代码关联,IIS就会调用这些代码来处理request请求。文件的扩展名被映射成可执行代码存储在IIS的metabase标记中。当ASP.NET被安装后,它会通过一个动态链接库aspnet_isapi.dll将各种不同的文件扩展名添加到matabase标记中,包括.apx和.asmx。
当IIS收到一个某一个页面文件发送的HTTP request请求时,它会调用aspnet_isapi.dll中的代码:使用一个命名管道将IIS服务器上的inetinfo.exe发送来的request请求转发到ASP.NET工作进程的一个实例:aspnet_wp.exe中。(在Windows Server 2003中, IIS 6.0kernel-mode模式的HTTP listener, 允许 request请求不通过inetinfo.exe而直接从操作系统转发到工作进程。)工作进程使用一个HttpRuntime类的实例来处理request请求。图-3 展示了完整的机制。
图-3
HTTP管道程序在工作进程的实例中处理request请求。默认地,在某一时刻仅仅只能有一个工作进程工作(如果你的web服务器有多个CPU,你可以配置管道程序使用多个工作进程),这是一个在本地IIS上很重要的一个改变,它使用不同的工作进程主要为了隔离不同的application程序。同时管道程序的各个工作进程也完全地被AppDomain所隔离,你可以将AppDomain看作是进程中的一个子进程。管道程序向一个AppDomain中的所有虚拟目录发送HTTP request请求。换句话说,每一个虚拟目录被作为一个单独的应用程序对待。这还有另外一个本地IIS值得注意的改变就是允许多个虚拟目录成为同一个application的一部分。
ASP.NET支持基于很多标准的循环工作进程,这些标准包括空闲时间、requests serviced的数量、requests队列的数量以及物理内存的耗费量。全局.NET配置文件和machine配置文件初始化这些数值(好象processModel的元素)。当一个aspnet_wp.exe的实例 crosses one of these thresholds, aspnet_isapi.dll会运行一个新的工作进程并开始发送request请求。旧的实例在它处理完request请求后会自动终止。循环工作进程会提升可靠性通过在这些进程耗尽资源之前杀死它们。
ASP.NET支持基于很多标准的循环工作进程,这些标准包括空闲时间、requests serviced的数量、requests队列的数量以及物理内存的耗费量。全局.NET配置文件和machine配置文件初始化这些数值(好象processModel的元素)。当一个aspnet_wp.exe的实例 crosses one of these thresholds, aspnet_isapi.dll会运行一个新的工作进程并开始发送request请求。旧的实例在它处理完request请求后会自动终止。循环工作进程会提升可靠性通过在这些进程耗尽资源之前杀死它们。
三、HTTP Handlers
HTTP handlers 是一个继承自IHttpHandler接口的简单类,以下是该类的详细代码:
interface IHttpHandler
{
// called to process request and generate response
void ProcessRequest(HttpContext ctx);
// called to see if handler can be pooled
bool IsReuseable { get; }
}
Handlers也可以继承自IHttpAsyncHandler接口,如果想要它们支持异步调用。
HttpApplicaiton对象会调用ProcessRequest方法,通过handler来处理当前的HTTP请求和产生response响应。在此期间IsReuseable属性会被访问为类测定hanlder是否可以被再使用。
图-4的代码实现了一个简单的可以重用的HTTP句柄,它可以响应所有的request请求并返回当前的时间在一个XML标记中。你也可以使用使用HttpContext对象的response属性来设置response信息的MIME属性从而输出自己的内容。
using System;
using System.Web;
namespace Pipeline
{
public class TimeHandler : IHttpHandler
{
void ProcessRequest(HttpContext ctx)
{
ctx.Response.ContentType = "text/xml";
ctx.Response.Write("<now>");
ctx.Response.Write(
DateTime.Now.ToString());
ctx.Response.Write("</now>");
}
bool IsReuseable { get { return true; } }
}
}
图-4:TimeHandler
当HTTP handler类被实现时,它一定是被配置好的。配置会分为三个阶段:第一、你应该将编译好的代码放到ASP.NET工作进程能够找到的地方。一般地,已编译好的.NET文件(一般是dll文件)应该位于web服务器的虚拟目录下的bin文件夹中或位于全局编译缓存中(GAC)。
第二步:在HTTP request请求到来时,你应该让HTTP的管道程序执行你的代码。你可以通过在你虚拟目录下的web.config文件中去添加<httpHandlers>标签。如下:
<configuration>
<system.web>
<httpHandlers>
<add verb="GET" path="*.time"
type="Pipeline.TimeHandler,
Pipeline"
/>
</httpHandlers>
</system.web>
</configuration>
以上的代码可以作为附加的信息添加到.config的配置文件中。例如,这个web.config文件会告诉asp.net的HTTP管道程序处理所有.time文件的GET请求通过调用编译文件中的Pipeline.TimeHandler类。
最后,这些.time文件的request请求会被IIS转发到aspnet_isapi.dll,以便这些请求可以被管道程序第一时间处理。而且这些request请求也会在IIS的metabase中添加一个新的文件mapping映射。不过还有更简单的方式,就是直接使用IIS管理台将虚拟目录的文件扩展名映射到Application的配置对话框中。如图-5:
图-5
除了实现已有的客户端handler外,你也可以写自己的handler factoriey。一个handler factoriey就是一个实现了IHttpHandlerFactory的类。Handler factiory的配置方法和普通handlers一样,唯一不同的是原先web.config文件中的handler类都将被factory类所代替。
四、标准Handlers
一些高级的ASP.NET技术,例如pages和Web Services都是通过顶层HTTP handler直接创建的。以下是通过.config文件来配置<httpHandlers>部分:
<httpHandlers> entries:
<httpHandlers>
<add verb="*" path="*.ashx"
type="System.Web.UI.SimpleHandlerFactory"
/>
<add verb="*" path="*.aspx"
type="System.Web.UI.PageHandlerFactory"
/>
<add verb="*" path="*.asmx"
type="System.Web.Services.Protocols.
WebServiceHandlerFactory ... "
/>
</httpHandlers>
第一个实体映射扩展名为.ashx的文件到SimpleHandlerFactory类,使得一个HTTP handler factory知道如何从.ashx源文件中安装、编译和执行一个IHttpHandler。然后结果对象就可以被HTTP管道程序直接使用。
图-6展示了使用.ashx文件重写的一个TimeHandler例子。@WebHandler部分告诉SimpleHandlerFactory Http handler类的名字在源代码编译后。这么做最大的好处就在它的配置非常简单:你只需要将所有的.ashx文件拷贝到虚拟目录下,而不需要再创建或修改web.config文件或在.NET安装完后再更新IIS。
<%@ WebHandler language="C#"
class="Pipeline.TimeHandler" %>
using System;
using System.Web;
namespace Pipeline
{
public class TimeHandler : IHttpHandler
{
void ProcessRequest(HttpContext ctx)
{
// set response message MIME type
ctx.Response.ContentType = "text/xml";
// write response message body
ctx.Response.Write("<now>");
ctx.Response.Write(
DateTime.Now.ToString());
ctx.Response.Write("</now>");
}
bool IsReuseable { get { return true; } }
}
}
图-6
第二个<httpHandlers>部分的实体将.aspx文件扩展名映射到PageHandlerFactory类,主要为了让HTTP handler factory知道如何将.aspx的源代码编译成一个System.Web.UI.Page-derived类。这个Page类实现了IHttpHandler借口,因此最终对象可以被HTTP管道程序直接使用。
第三个实体将.asmx的扩展名映射到WebServiceHandlerFactory类,这么做主要为了让一个HTTP handler factory知道如何将一个.asmx文件中的源代码编译并实例化。然后它会绑定一个标准的HTTP handler(默认的为SyncSessionlessHandler)实例并使用反射机制将SOAP信息转化成方法的调用参数。最后,最终对象就可以被HTTP管道程序直接使用了。
这里需要值得注意的是PageHandlerFactory、WebServiceHandlerFactory和SimpleHandlerFactory类并不能在每一个request请求上都编译.aspx、.asmx和.ashx文件。作为替代,这些编译好的代码会被缓存在ASP.NET安装目录下的临时文件中。并且当源代码改变时,这些代码仅仅只会被编译一次。
这里需要值得注意的是PageHandlerFactory、WebServiceHandlerFactory和SimpleHandlerFactory类并不能在每一个request请求上都编译.aspx、.asmx和.ashx文件。作为替代,这些编译好的代码会被缓存在ASP.NET安装目录下的临时文件中。并且当源代码改变时,这些代码仅仅只会被编译一次。
五、HTTP Modules
HTTP handlers是HTTP通信的最终部分。Handler类的实例专门用来接收HTTP请求并产生response响应。HTTP modules作为过滤器在request和response信息穿过管道程序时处理它们(可以是检查并修改这些信息的内容)。管道程序可以用这些HTTP modules特别安全地实现自己的底层处理程序。
HTTP modules 是实现IHttpModule接口的简单类:
interface IHttpModule
{
// called to attach module to app events
void Init(HttpApplication app);
// called to clean up
void Dispose()
}
当module被第一次创建时,Init方法会被HttpApplication对象调用,它可以通过HttpApplication对象将一个或多个事件handlers绑定到事件上。
图-7的代码展示了一个HTTP module如何处理HttpApplication对象的BeginRequest和EndRequest事件。在这个例子中,Init方法使用了常见的.NET技术将module的OnBeginRequst和OnEndRequest作为事件句柄绑定到HttpApplication对象上。OnBeginRequest主要作用是获取存储当前时间并将时间存放到变量start中。而OnEndRequest主要作用是计算OnBeginRequest和OnEndRequest之间的运行时间差并将这段时间添加到客户端HTTP header中。
OnEndRequest方法的最大优势在于第一个参数实际上传递的module绑定的HttpApplication对象。并且当前的信息会做为一个HttpApplication对象的属性(Http-Context)被OnEndRequest方法使用。
using System;
using System.Web;
namespace Pipeline
{
public class ElapsedTimeModule : IHttpModule
{
DateTime start;
public void Init(HttpApplication app)
{
// register for pipeline events
app.BeginRequest +=
new EventHandler(this.OnBeginRequest);
app.EndRequest +=
new EventHandler(this.OnEndRequest);
}
public void Dispose() {}
public void OnBeginRequest(object o,
EventArgs args)
{
// record time when request started
start = DateTime.Now;
}
public void OnEndRequest(object o,
EventArgs args)
{
// measure elapsed time
TimeSpan elapsed =
DateTime.Now - start;
// get access to app and context
HttpApplication app =
(HttpApplication) o;
HttpContext ctx = app.Context;
// add custom header to HTTP response
ctx.Response.AppendHeader(
"ElapsedTime",
elapsed.ToString());
}
}
}
图-7
在一个HTTP module类被实现和使用之间,必须对它做一定的配置。配置的步骤包括两步。首先你应该将编译好的module代码放到web服务器上的站点目录下的bin文件夹中以至于ASP.NET的工作进程能够找到它。
然后你应该在你的web.config文件中添加<httpModules>部分,如下:
然后你应该在你的web.config文件中添加<httpModules>部分,如下:
<configuration>
<system.web>
<httpModules>
<add
name="Elapsed"
type="Pipeline.ElapsedTimeModule, Pipeline"
/>
</httpModules>
</system.web>
</configuration>
这个例子中,web.config文件会告诉ASP.NET的HTTP管道程序将Pipeline.ElapsedTimeModule绑定到每一个HttpApplication对象用来处理这个虚拟目录下的service请求。
欢迎添加我的公众号一起深入探讨技术手艺人的那些事!
如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!