服务器

位置:IT落伍者 >> 服务器 >> 浏览文章

httpModules与httpHandlers


发布日期:2021年04月01日
 
httpModules与httpHandlers

ASPNET对请求处理的过程

当请求一个*aspx文件的时候这个请求会被inetinfoexe进程截获它判断文件的后缀(aspx)之后将这个请求转交给ASPNET_ISAPIdllASPNET_ISAPIdll会通过http管道(Http PipeLine)将请求发送给ASPNET_WPexe进程在ASPNET_WPexe进程中通过HttpRuntime来处理这个请求处理完毕将结果返回客户端

inetinfoexe进程是www服务的进程IIS服务和ASPNET_ISAPIDLL都寄存在此进程中

ASPNET_ISAPIDLL是处理aspx文件的win组件其实IIS服务器是只能识l文件的当IIS服务器发现被请求的文件是aspx文件时IIS服务器将其交给aspnet_isapidll来处理

aspnet_wpexe进程ASPNET框架进程提运行的托管环境的CLR(公共语言运行时)就是寄存在此进程中

ASPNET Framework处理一个Http Request的流程

HttpRequest>inetinfoexe>ASPNET_ISAPIdll>ASPNET_WPexe>HttpRuntime>HttpApplication Factory>HttpApplication>HttpModule>HttpHandler Factory>HttpHandler>HttpHandlerProcessRequest()

ASPNET请求处理过程是基于管道模型的这个管道模型是由多个HttpModule和HttpHandler组成ASPNET把http请求依次传递给管道中各个HttpModule最终被HttpHandler处理处理完成后再次经过管道中的HTTP模块把结果返回给客户端我们可以在每个HttpModule中都可以干预请求的处理过程

注意在http请求的处理过程中只能调用一个HttpHandler但可以调用多个HttpModule

当请求到达HttpModule的时候系统还没有对这个请求真正处理但是我们可以在这个请求传递到处理中心(HttpHandler)之前附加一些其它信息或者截获的这个请求并作一些额外的工作也或者终止请求等在HttpHandler处理完请求之后我们可以再在相应的HttpModule中把请求处理的结果进行再次加工返回客户端

HttpModule

HTTP模块是实现了SystemWebIhttpModule接口的类

IHttpModule接口的声明

public interface IHttpModule

{

void Init (HttpApplication context);

void Dispose ();

}

Init 方法系统初始化的时候自动调用这个方法允许HTTP模块向HttpApplication 对象中的事件注册自己的事件处理程序

Dispose方法 这个方法给予HTTP模块在对象被垃圾收集之前执行清理的机会此方法一般无需编写代码

HTTP模块可以向SystemWebHttpApplication对象注册下面一系列事件

AcquireRequestState 当ASPNET运行时准备好接收当前HTTP请求的对话状态的时候引发这个事件

AuthenticateRequest 当ASPNET 运行时准备验证用户身份的时候引发这个事件

AuthorizeRequest 当ASPNET运行时准备授权用户访问资源的时候引发这个事件

BeginRequest 当ASPNET运行时接收到新的HTTP请求的时候引发这个事件

Disposed 当ASPNET完成HTTP请求的处理过程时引发这个事件

EndRequest 把响应内容发送到客户端之前引发这个事件

Error 在处理HTTP请求的过程中出现未处理异常的时候引发这个事件

PostRequestHandlerExecute 在HTTP处理程序结束执行的时候引发这个事件

PreRequestHandlerExecute 在ASPNET开始执行HTTP请求的处理程序之前引发这个事件在这个事件之后ASPNET 把该请求转发给适当的HTTP处理程序

PreSendRequestContent 在ASPNET把响应内容发送到客户端之前引发这个事件这个事件允许我们在内容到达客户端之前改变响应内容我们可以使用这个事件给页面输出添加用于所有页面的内容例如通用菜单头信息或脚信息

PreSendRequestHeaders 在ASPNET把HTTP响应头信息发送给客户端之前引发这个事件在头信息到达客户端之前这个事件允许我们改变它的内容我们可以使用这个事件在头信息中添加cookie和自定义数据

ReleaseRequestState 当ASPNET结束所搜有的请求处理程序执行的时候引发这个事件

ResolveRequestCache 我们引发这个事件来决定是否可以使用从输出缓沖返回的内容来结束请求这依赖于Web应用程序的输出缓沖时怎样设置的

UpdateRequestCache 当ASPNET完成了当前的HTTP请求的处理并且输出内容已经准备好添加给输出缓沖的时候引发这个事件这依赖于Web应用程序的输出缓沖是如何设置的

上面这么多的事件我们看起来可能会有些眼晕但没关系下面一步一步地看

HttpModule生命周期示意图

下面是事件的触发顺序

BeginRequest和PreRequestHandlerExecute之间的事件是在服务器执行HttpHandler处理之前触发

PostRequestHandlerExecute和PreSendRequestContent之间的事件是在服务器执行Handler处理之后触发

下面我们看一下如何使用HttpModule来实现我们日常的应用

HttpModule通过在某些事件中注册把自己插入ASPNET请求处理管道当这些事件发生的时候ASPNET调用对相应的HTTP模块这样该模块就能处理请求了

向每个页面动态添加一些备注或说明性的文字

有的网站每一个页面都会弹出一个广告或在每个页面都以注释形式(<! >)加入网站的版权信息如果在每个页面教编写这样的JS代码的话对于大一点的网站这种JS代码的编写与维护可是一个很繁琐枯燥的工作

有了HttpModule我们就可以很简单地解决这个问题了HttpModule是客户端发出请求到客户端接收到服务器响应之间的一段必经之路我们完全可以在服务器处理完请求之后并在向客户端发送响应文本之前这段时机把这段注释文字添加到页面文本之后这样每一个页面请求都会被附加上这段注释文字

这段代码究竟该在哪个事件里实现呢? PostRequestHandlerExecute和PreSendRequestContent之间的任何一个事件都可以但我比较喜欢在EndRequest事件里编写代码

第一步创建一个类库ClassLibrary

第二步编写一个类实现IHttpModule接口

class TestModule:IHttpModule

{

public void Dispose()

{

}

public void Init(HttpApplication context)

{

}

}

第三步在Init事件中注册EndRequest事件并实现事件处理方法

class TestModule:IHttpModule

{

public void Dispose(){}

public void Init(HttpApplication context)

{

contextEndRequest += new EventHandler(context_EndRequest);

}

void context_EndRequest(object sender EventArgs e)

{

HttpApplication ha = (HttpApplication)sender;

haResponseWrite(<!这是每个页面都会动态生成的文字grayworm>);

}

}

第四步在WebConofig中注册一下这个HttpModule模块

<httpModules>

<add name=TestModule type=ClassLibraryTestModuleClassLibrary></add>

</httpModules>

name模块名称一般是类名

type有两部分组成前半部分是命名空间和类名组成的全名后半部分是程序集名称如果类是直接放在App_Code文件夹中那程序名称是App_Code

这样在Web站点是添加该类库的引用后运行每个页面会发现其源文件中都会加入<!这是每个页面都会动态生成的文字grayworm>这句话同样的方法你也可以在其中加入JS代码

身份检查

大家在作登录时登录成功后一般要把用户名放在Session中保存在其它每一个页面的Page_Load事件中都检查Session中是否存在用户名如果不存在就说明用户未登录就不让其访问其中的内容

在比较大的程序中这种做法实在是太笨拙因为你几乎要在每一个页面中都加入检测Session的代码导致难以开发和维护下面我们看看如何使用HttpModule来减少我们的工作量

由于在这里我们要用到Session中的内容我们只能在AcquireRequestState和PreRequestHandlerExecute事件中编写代码因为在HttpModule中只有这两事件中可以访问Session这里我们选择PreRequestHandlerExecute事件编写代码

第一步创建一个类库ClassLibrary

第二步编写一个类实现IHttpModule接口

class TestModule:IHttpModule

{

public void Dispose()

{

}

public void Init(HttpApplication context)

{

}

}

第三步在Init事件中注册PreRequestHandlerExecute事件并实现事件处理方法

class AuthenticModule:IHttpModule

{

public void Dispose(){}

public void Init(HttpApplication context)

{

contextPreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);

}

void context_PreRequestHandlerExecute(object sender EventArgs e)

{

HttpApplication ha = (HttpApplication)sender;

string path = haContextRequestUrlToString();

int n = pathToLower()IndexOf(Loginaspx);

if (n == ) //是否是登录页面不是登录页面的话则进入{}

{

if (haContextSession[user] == null) //是否Session中有用户名若是空的话转向登录页

{

haContextResponseRedirect(Loginaspx?source= + path);

}

}

}

}

第四步在Loginaspx页面的登录按钮中加入下面代码

protected void Button_Click(object sender EventArgs e)

{

if(true) //判断用户名密码是否正确

{

if (RequestQueryString[source] != null)

{

string s = RequestQueryString[source]ToLower()ToString(); //取出从哪个页面转来的

Session[user] = txtUIDText;

ResponseRedirect(s); //转到用户想去的页面

}

else

{

ResponseRedirect(mainaspx); //默认转向mainaspx

}

}

}

第五步在WebConofig中注册一下这个HttpModule模块

<httpModules>

<add name=TestModule type=ClassLibraryTestModuleClassLibrary></add>

</httpModules>

多模块的操作

如果定义了多个HttpModule在nfig文件中引入自定义HttpModule的顺序就决定了多个自定义HttpModule在处理一个HTTP请求的接管顺序

HttpHandler

HttpHandler是HTTP请求的处理中心真正地对客户端请求的服务器页面做出编译和执行并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中

HttpHandler与HttpModule不同一旦定义了自己的HttpHandler类那么它对系统的HttpHandler的关系将是覆盖关系

IHttpHandler接口声明

public interface IHttpHandler

{

bool IsReusable { get; }

public void ProcessRequest(HttpContext context); //请求处理函数

}

示例把硬盘上的图片以流的方式写在页面上

class TestHandler : IHttpHandler

{

public void ProcessRequest(HttpContext context)

{

FileStream fs = new FileStream(contextServerMapPath(wormjpg) FileModeOpen);

byte[] b = new byte[fsLength];

fsRead(b (int)fsLength);

fsClose();

contextResponseOutputStreamWrite(b bLength);

}

public bool IsReusable

{

get

{

return true;

}

}

}

WebConfig配置文件

<httpHandlers>

<add verb=* path=* type=ClassLibraryTestHandlerClassLibrary></add>

</httpHandlers>

Verb属性指定了处理程序支持的HTTP动作*-支持所有的HTTP动作;GET-支持Get操作;POST-支持Post操作;GET POST-支持两种操作

Path属性指定了需要调用处理程序的路径和文件名(可以包含通配符)**aspxshowImageaspxtestaspxtestaspx

Type属性用名字空间类名称和程序集名称的组合形式指定处理程序或处理程序工厂的实际类型ASPNET运行时首先搜索bin目录中的DLL接着在GAC中搜索

这样程序运行的效果是该网站的任何一个页面都会显示wormjpg图片如何只让一个页面(defaultaspx)执行HttpHandler中的ProcessRequest方法呢?最简单的办法是在WebConfig文件中把path配置信息设为defaultaspx

根据这个例子大家可以考虑一下如何编写验证码

IHttpHandler工厂

IHttpHandlerFactory的作用是对IHttpHandler进行管理工厂的作用请见ml

IHttpHandlerFactory接口的声明

public interface IHttpHandlerFactory

{

IHttpHandler GetHandler (HttpContext contextstring requestTypestring urlstring pathTranslated);

void ReleaseHandler (IHttpHandler handler);

}

GetHandler返回实现IHttpHandler接口的类的实例ReleaseHandler使工厂可以重用现有的处理程序实例

示例两个用IHttpHandlerFactory来实现对不同HttpHandler的调用

有两个HttpHandler将图片显示在页面上的HttpHandler和生成验证码的Handler

//将图片显示在页面上的Handler

class TestHandler : IHttpHandler

{

public void ProcessRequest(HttpContext context)

{

FileStream fs = new FileStream(contextServerMapPath(wormjpg) FileModeOpen);

byte[] b = new byte[fsLength];

fsRead(b (int)fsLength);

fsClose();

contextResponseOutputStreamWrite(b bLength);

}

public bool IsReusable

{

get

{

return true;

}

}

}

//生成验证码的Handler

class CodeHandler:IHttpHandler

{

public bool IsReusable

{

get

{

return true;

}

}

public void ProcessRequest(HttpContext context)

{

Image b = new Bitmap();

Graphics g = GraphicsFromImage(b);

SolidBrush sb = new SolidBrush(ColorWhite);

Font f = new Font(宋体 );

string str = ;

Random r = new Random();

for (int i = ; i < ; i++)

{

str += rNext();

}

gDrawString(strfsb);

bSave(contextResponseOutputStream SystemDrawingImagingImageFormatJpeg);

}

}

IHttpHandler工厂

class TestHandlerFactory : IHttpHandlerFactory

{

public IHttpHandler GetHandler(HttpContext context string requestType string url string pathTranslated)

{

string fname = urlSubstring(urlIndexOf(/) + );

while (fnameIndexOf(/) != )

fname = fnameSubstring(fnameIndexOf(/) + );

string cname = fnameSubstring( fnameIndexOf());

string className =;

className = ClassLibraryCodeHandler;

object h = null;

try

{

//h = new TestHandler();

h = ActivatorCreateInstance(TypeGetType(className));

}

catch (Exception e)

{

throw new HttpException(工厂不能为类型 + cname + 创建实例 e);

}

return (IHttpHandler)h;

}

public void ReleaseHandler(IHttpHandler handler)

{

}

}(车延禄)

配置文件

<httpHandlers>

<add verb=* path=defaultaspxdefaultaspx type=ClassLibraryTestHandlerFactoryClassLibrary></add>

</httpHandlers>

这样TestHandlerFactory就会根据请求的不同页面执行不同的HttpHandler处理程序了

HttpHandler使用会话

如果要在处理程序中使用Session那必须把该HttpHandler实现IRequiresSessionState接口IRequiresSessionState接口是个空接口它没有抽象方法只是一个标记此处就不作例子验证了

ASPNet处理Http Request时使用Pipeline(管道)方式由各个HttpModule对请求进行处理然后到达 HttpHandlerHttpHandler处理完之后仍经过Pipeline中各个HttpModule的处理最后将HTML发送到客户端浏览 器中

生命周期中涉及到几个非常重要的对象HttpHandlerHttpModuleIHttpHandlerFactory他们的执行(顺序)大致的执行过程是这样的client端发送页面请求被IIS的某个进程截获它根据申请的页 面后缀(aspx)不同调用不同的页面处理程序(asp>aspdll; aspx>ISAPIdll)而页面处理程序在处理过程中则要经历HttpModuleHttpHandler的处理前者HttpModule用于页面处理前和处理后的一些事件的处理后者HttpHandler进行真正的页面的处理

如前所说HttpModule会在页面处理前和后对页面进行处理所以它不会影响真正的页面请求通常用在给每个页面的头部或者尾部添加一些信息(如版 权声明)等曾经见过一些免费的空间我们的页面上传上去后浏览的时候发现在每个页面的头部和尾部多了很多小广告如果理解了 HttpModule的原理要做这个就不是很难了~

IHttpModule与IHttpHandler的区别整理

先后次序先IHttpModule后IHttpHandler 注:Module要看你响应了哪个事件一些事件是在Handler之前运行的一些是在Handler之后运行的

对请求的处理上:

IHttpModule是属于大小通吃类型无论客户端请求的是什么文件都会调用到它;例如aspxrarhtml的请求

IHttpHandler则属于挑食类型只有注册过的文件类型(例如aspxasmx等等)才会轮到调用它

IHttpHandler按照你的请求 生成响应的内容IHttpModule对请求进行预处理如验证修改过滤等等同时也可以对响应进行处理

ASPNet系统本身配置有很多HttpHandler和HttpModule以处理aspx等Net标准的页面文件以及这些页面文件中标 准的事件处理等查看%System%/MicrosoftNET\Framework\v\CONFIG目录下的 nfig文件中的httpHandlers和httpModules节点可以看到这些配置如果有兴趣可以使用Reflector查 看Net系统中相关的类和方法了解Net如何处理以及做了什么处理

Net也提供了一套机制来开发自定义的HttpHandler和 HttpModule均可以用于对HttpRequest的截取完成自定义的处理 HttpModule 继承SystemWebIHttpModule接口实现自己的HttpModule类必须要实现接口的两个方法Init和Dispose在 Init中可以添加需要截取的事件Dispose用于资源的释放如果在Init中创建了自己的资源对象请在Dispose中进行释放

namespace MyModule

{

public class MyHttpModule : IHttpModule

{

public MyHttpModule()

{

}

//Init方法用来注册HttpApplication 事件

public void Init(HttpApplication r_objApplication)

{

r_objApplicationBeginRequest += new EventHandler(thisBeginRequest);

}

public void Dispose()

{

}

private void BeginRequest(object r_objSender EventArgs r_objEventArgs)

{

HttpApplication objApp = (HttpApplication)r_objSender;

objAppResponseWrite(您请求的URL为 + objAppRequestPath);

}

}

}

将编译的dll文件拷贝到web项目的bin目录下在web项目的nfig文件systemweb节点中配置

这样就将自定义的HttpModule类MyHttpModule插入到了当前web的HttpModule的Pipeline中 HttpModule主要功能是对Application的各个事件进行截取在这些事件中完成自己的处理其实如果自己开发一些项目直接在 Globalasax中处理已经足够了如果是开发一个Framework或者是某些方面的组件需要在事件中添加处理开发自定义的 HttpModule可以避免使用Framework或者组件时还得手工在Globalasax中添加代码 目前想到的开发自定义HttpModule的用途有全局的身份/权限验证自定义网站访问/操作日志的记录处于管理/调试等目的对站点进行监控追蹤 等当然如果是结合自定义的HttpHandler进行Framework的开发HttpModule可以用于其它的一些特殊的处理

<httpModules>

<add name=test type=MyHttpModuleTestMyHttpModuleMyHttpModule/>

</httpModules>

注意要区分大小写因为nfig作为一个XML文件是大小写敏感的type=MyHttpModuleTestMyHttpModuleMyHttpModule告诉我们

系统将会将http request请求交给位于MyHttpModuledll文件中的MyHttpModuleTestMyHttpModule类去处理

HttpHandler是完全的对Http Request的截取

首先继承SystemWebIHttpHandler接口实现自己的HttpHandler类必须要实现接口的ProcessRequest方 法和IsReusable属性ProcessRequest方法中完成对每个Http Request的处理发送处理结果的HTML到输出缓存中IsReusable属性被Net Framework调用用以确定这个HttpHandler的实例是否可以被重用于同类型其它的Request处理

如果你在自己的HttpHandler类中需要读取或者是写Session值需要再继承一个接口IRequiresSessionState这个接 口没有任何方法只是一个标记接口继承这个接口之后就可以在自己的HttpHandler中访问Session可以在Session中写入值

namespace MyHandler

{

public class MyHttpHandler : IHttpHandler IRequiresSessionState

{

public MyHttpHandler() {}

public bool IsReusable

{

get { return true; }

}

public void ProcessRequest(HttpContext context)

{

HttpResponse objResponse = contextResponse ;

objResponseWrite(

This request is handled by MyHttpHandler

);

}

}

}

把编译的dll文件拷贝到web项目的bin目录下

接下来这样来测试一下MyHttpHandler我们为IIS配置一个为后缀名的文件类型用我们写的MyHttpHandler来处理

首先在IIS站点的Configuration配置里面添加一个后缀名处理的Application Extention Mapping项

然后在web项目的nfig节点节点中配置

MyHttpHandler MyHandler/>

verb属性配置这个HttpHandler处理那些HTTP方法例如GETPOST等如果是处理所有方法就用*path属性配置HttpHandler对哪些文件进行处理例如可以是如果是处理所有文件就用

这样这个站点上所类型文件的访问都由MyHttpHandler处理使//localhost/站点虚拟目录/访问测试站点可以看到测试效果当然这个文件在Web服务器上是并不存在的

对HttpHandler的使用比较典型的有Net的Web MVC开源项目MaverickMaverick使用一个Dispatcher类对所有的Http Request进行截取他以m作为后缀名向Web服务器提交请求在Dispatcher中m的后缀去掉提取Command Name然后以这个command name从配置文件中加载处理的flow形成一个chain依次对chain上的各个command和view进行处理对各个command和 view的处理结果可能会在chain中选择不同的处理分支每个处理的Step中将处理结果的HTML写入Response的缓存中进行输出

总体来说Maverick的框架架构概念很不错但也存在明显的缺陷以后有时间再详细的写写它的架构和需要改进之处

总之将HttpModuleHttpHandler以及使用Ajax等将客户端进行封装结合起来能够给web项目的开发带来非常大的改善空间

AspNet HttpHandler实现URL重写的

我们经常看到很多网站访问文章的时候才用的是**l 或***shtml (如本blog的日志访问效果)其时这写文件在服务器上不存在的那为什么会出现这样的效果呢是因为Web服务器上对URL执行了重写把访问的 URL根据特定的格式重写成内部访问页面来实现的它的好处是便于用户理解同时搜索引擎也能更好地收入你的网站当然其它的好处也很多这里不做一一介 绍了

本文所讲的是使用AspNet中的HttpHandler实现URL重写的它所实现的原理请看这里本程序可以处理任何Url因为我在程序中使用了URL过虑只有访问文件名是数字的才进行处理并指在内部执行一个新的页面并输出数据代码如下

public void ProcessRequest(HttpContext Context)

{

try {

//申明Request

HttpRequest Request = ContextRequest;

//取来路Url的绝对路径

string Url = RequestUrlAbsolutePath;

//取访问的Web文件的开始字符间隔数

int RegStart = UrlLastIndexOf(/) + ;

//申明一个确定Web文件名是否全是数字

Regex Reg = new Regex(@\d+);

//用正则表达式进行匹配

if (RegIsMatch(Url RegStart))

{

// 如果web文件名是数字则判定是查询相关文章执行指定页面 ContextServerExecute(~/PermaLinkaspx?id= + RegMatch(Url RegStart)Value);

}

}

catch

{

ContextResponseRedirect(ContextRequestUrlToString());

}

}

当然你首先要做的是先建一个类并继承自IHttpHandler然后把这段代码拷入并编译在Web项目中若要使用此功能需要在nfig里面加上如下语句

<httpHandlers>

<add verb=* path=*shtml type=HttpHandleUrlRewrite />

</httpHandlers>

同时还要在IIS中对Web项目进行配置在Web项目的属性中在主目录选项卡里把执行权限改为脚本和可执行文件然后打开配置在应用程序扩展里加上需重写的文件格式的扩展好了成事具备只欠运行了

               

上一篇:采用HttpModules来重写URLs

下一篇:如何封装JS和CSS文件为服务器端控件