ASP.NET中的HTTP模块和处理程式-.NET教程,Asp.Net研发

来源:互联网 发布:云平台数据安全 编辑:程序博客网 时间:2024/05/17 02:50
在internet时代的开端,客户端的需求很有限;.htm文档就能够满足他们的需求。但是,随着时间的流逝,客户端需求的扩充超越了.htm文档或静态文档所包含的功能。
  研发者需要扩充或扩展web服务器的功能。web服务器厂商设计了不同的解决方案,但是都遵循同一个主题“向web服务器插入某些组件”。任何的web服务器补充技术都允许研发者建立并插入组件以增强web服务器的功能。微软公司提出了isapi(internet服务器api),网景公司提出了nsapi(网景服务器api)等等。

  isapi是一种重要的技术,他允许我们增强和isapi兼容的web服务器(iis就是一种和isapi兼容的web服务器)的能力。我们使用下面的组件达到这个目的:

  · isapi扩展

  · isapi过滤器

  isapi扩展是使用win32动态链接库来实现的。您能够把isapi扩展看作是个普通的应用程式。isapi扩展的处理目标是http请求。这意味着您必须调用他们才能激活他们。 您能够认为isapi过滤器仅仅就是个过滤器而已。客户端每次向服务器发出请求的时候,请求要经过过滤器。客户端无需在请求中指定过滤器,只需要简单地把请求发送给web服务器,接着web服务器把请求传递给相关的过滤器。接下来过滤器可能修改请求,执行某些登录操作等等。

  由于这些组件的复杂性,实现他们很困难。研发者不得不使用c/c++来研发这些组件,但是对于很多人来说,使用c/c++进行研发简直就是痛苦的代名词。

  那么asp.net提供什么东西来实现这些功能呢?asp.net提供的是httphandler(http处理程式)和httpmodule(http模块)。

  在深入了解这些组件的周详信息之前,了解一下http请求经过http模块和http处理程式的时候的处理流程是有价值的。

  建立示例应用程式

  我建立了下面一些的c#项目以演示应用程式的不同组件:

  · newhandler (http处理程式)

  · webapp (演示http处理程式)

  · securitymodules (http模块)

  · webapp2 (演示http模块)

  这些应用程式的安装步骤:

  · 解开attached zip文档中的所以代码。

  · 建立两个虚拟目录webapp和webapp2;把这两个目录指向webapp和webapp2应用程式的实际物理目录。

  · 把newhandler项目中的newhandler.dll文档复制到webapp应用程式的bin目录。

  · 把securitymodules项目中的securitymodules.dll文档复制到webapp2应用程式的bin目录中。



  asp.net请求的处理过程

  asp.net请求处理过程是基于管道模型的,在模型中asp.net把http请求传递给管道中的任何模块。每个模块都接收http请求并有完全控制权限。模块能够用任何自认为适合的方式来处理请求。一旦请求经过了任何http模块,就最终被http处理程式处理。http处理程式对请求进行一些处理,并且结果将再次经过管道中的http模块:


  请注意在http请求的处理过程中,只能调用一个http处理程式,然而能够调用多个http模块。

  http处理程式

  http处理程式是实现了system.web.ihttphandler接口的.net组件。任何实现了ihttphandler接口的类都能够用于处理输入的http请求。http处理程式和isapi扩展有些类似。http处理程式和isapi扩展的差别在于在url中能够使用http处理程式的文档名称直接调用他们,和isapi扩展类似。

  http处理程式实现了下列方法:

方法名称 描述 processrequest 这个方法实际上是http处理程式的核心。我们调用这个方法来处理http请求。 isreusable 我们调用这个属性来决定http处理程式的实例是否能够用于处理相同其他类型的请求。http处理程式能够返回true或false来表明他们是否能够重复使用。
  您能够使用web.config或machine.config文档把这些类映射到http请求上。映射完成以后,当接收到相应请求的时候asp.net会实例化http处理程式。我们将解释如何在web.config和/或machine.config文档中定义任何这些细节信息。

  asp.net还通过ihttphandlerfactory接口支持http处理程式的扩展。asp.net提供了把http请求路由到实现ihttphandlerfactory接口的类的对象上的能力。此外,asp.net还利用了factory设计模式。这种模式为建立一组相关对象而不提供具体类的功能提供了接口。简单的说,您能够把用于建立依赖传递进来的参数建立的http处理程式对象的类看作是factory(工厂)。我们不用指定需要实例化的特定的http处理程式;http处理程式工厂处理这种事务。这样做的长处在于假如未来实现ihttphandler接口的对象的实现方法发生了改变,只要接口仍然相同,客户端就不会受到影响。

  下面是ihttphandlerfactory接口中的方法列表:

方法名称 描述 gethandler 这个方法负责建立适当的处理程式并把他的指针返回到调用代码(asp.net运行时)。这个方法返回的处理程式对象应该实现了ihttphandler接口。 releasehandler 这个方法负责在请求处理完成后释放http处理程式。factory 实现决定了他的操作。factory 实现能够是实际摧毁实例,也能够把他放入缓冲池供以后使用。
  在配置文档中注册http处理程式和http处理程式工厂

  asp.net在下面的配置文档中维护自己的配置信息:

  · machine.config

  · web.config

  machine.config文档包含应用于电脑上安装的任何web应用程式的配置配置信息。

  web.config文档对于每个web应用程式来说是特定的。每个web应用程式都有自己的web.config文档。web应用程式的任何子目录也可能包含自己的web.config文档;这使得他们能够覆盖父目录的配置信息。
为了给我们的web应用程式添加http处理程式,您能够使用<httphandlers>和<add>节点。实际上,处理程式都带有<add>节点,列举在<httphandlers>和</httphandlers>节点之间。下面是添加http处理程式的一个普通的例子:

<httphandlers>
 <add verb="supported http verbs" path="path" type="namespace.classname, assemblyname" />
<httphandlers>
  在上面的xml中,

  · verb属性指定了处理程式支持的http动作。假如某个处理程式支持任何的http动作,请使用“*”,否则使用逗号分隔的列表列出支持的动作。因此假如您的处理程式只支持http get和post,那么verb属性就应该是“get, post”。

  · path属性指定了需要调用处理程式的路径和文档名(能够包含通配符)。例如,假如您希望自己的处理程式只有在test.xyz文档被请求的时候才被调用,那么path属性就包含“test.xyz”,假如您希望含有.xyz后缀的任何文档都调用处理程式,path属性应该包含“*.xyz”。

  · type属性用名字空间、类名称和部件名称的组合形式指定处理程式或处理程式工厂的实际类型。asp.net运行时首先搜索应用程式的bin目录中的部件dll,接着在全局部件缓冲(gac)中搜索。


  asp.net运行时对http处理程式的使用方式

  无论您是否相信,asp.net都使用http请求实现了大量的自己的功能。asp.net使用处理程式来处理.aspx、 .asmx、 .soap和其他asp.net文档。

  下面是machine.config文档中的一个片段:

<httphandlers>
 <add verb="*" path="trace.axd" type="system.web.handlers.tracehandler"/>
 <add verb="*" path="*.aspx" type="system.web.ui.pagehandlerfactory"/>
 <add verb="*" path="*.ashx" type="system.web.ui.simplehandlerfactory"/>
 <add verb="*" path="*.config" type="system.web.httpforbiddenhandler"/>
 <add verb="get,head" path="*" type="system.web.staticfilehandler"/>
 . . . . . .
 . . . . . .
</httphandlers>
  在上面的配置信息中您能够看到对.aspx文档的任何请求都由system.web.ui.pagehandlerfactory类来处理。和此类似,对.config文档和其他文档(他们不能被客户端直接访问)的任何请求都由system.web.httpforbiddenhandler类处理。您可能已猜到,当访问这些文档的时候,该类简单地给客户端返回一个错误信息。

  执行http处理程式

  现在您将看到如何实现一个http处理程式。那么我们的新处理程式要做什么任务呢?前面我提到,处理程式大多数用于给web服务器添加新功能;因此,我将建立一个处理程式来处理新的文档类型——扩展名为.15seconds的文档。我们建立了这个处理程式并在我们的web应用程式的web.config文档中注册之后,任何对.15seconds文档的请求都将由这个新处理程式来处理。

  您可能正在考虑这个处理程式的使用方法。假如您希望引入一种新的服务器脚本语言或动态服务器文档(例如asp、aspx)该怎么办呢?您能够为他编写一个自己的处理程式。类似地,假如您希望在iis上运行java小程式、jsp和其他一些服务器端java组件应该怎么办呢?一种方法是安装某些isapi扩展(例如allaire或macromedia jrun)。您也能够编写自己的http处理程式。尽管这对于第三方厂商(例如allaire和macromedia)来说是很复杂的事务,但是他却是个很有吸引力的选择,因为他们的http处理能够能够访问asp.net运行时暴露的任何新功能。

  实现我们的http处理程式包含以下步骤:

  1.编写一个实现ihttphandler接口的类。

  2. 在web.config或machine.config文档中注册这个处理程式。

  3.在internet服务管理器中把文档扩展(.15seconds)映射到asp.net isapi扩展dll(aspnet_isapi.dll)上。

  第一步

  在visual studio.net中建立一个新的c#类库项目,并把他命名为“myhandler”。visual studio.net将自动地给项目添加一个叫做“class1.cs”的类。把他改名为“newhandler”;在代码窗口中打开这个类,并把类的名称和构造函数的名称改成“newhandler”。

  下面是newhandler类的代码:

using system;
using system.web;

namespace myhandler
{
 public class newhandler : ihttphandler
 {
  public newhandler()
  {
   // todo: 此处添加构造逻辑
  }

  #region implementation of ihttphandler
  public void processrequest(system.web.httpcontext context)
  {
   httpresponse objresponse = context.response ;
   objresponse.write("<html><body><h1>hello 15seconds reader ") ;
   objresponse.write("</body></html>") ;
  }

  public bool isreusable
  {
   get
   {
    return true;
   }
  }
  #endregion
 }
}
  您在processrequest方法中能够看到,该http处理程式通过system.web.httpcontext对象访问了任何作为参数传递给他的asp.net内部对象。实现processrequest方法只需要简单地从context对象中提取httpresponse对象并把发送一些html给客户端。类似地,isreusable返回true,表明这个处理程式能够被重复用作处理其他的http请求。

  我们编译上面的代码并把他放到webapp虚拟目录的bin目录之中。

  第二步

  在web.config文档中通过添加下面的文本来注册这个处理程式:

<httphandlers>
 <add verb="*" path="*.15seconds" type="myhandler.newhandler,myhandler"/>
</httphandlers>
  第三步

  由于我们已建立了用于处理新扩展文档的处理程式了,我们还需要把这个扩展名告诉iis并把他映射到asp.net。假如您不执行这个步骤而试图访问hello.15seconds文档,iis将简单地返回该文档而不是把他传递给asp.net运行时。其结果是该http处理程式不会被调用。

  运行internet服务管理器,右键点击默认web站点,选择属性,移动到home目录选项页,并点击配置按钮。应用程式配置对话框弹出来了。点击添加按钮并在可执行字段输入aspnet_isapi.dll文档路径,在扩展字段输入.15seconds。其他字段不用处理;该对话框如下所示:


  点击确认按钮关闭应用程式配置和默认web站点属性对话框。

  现在我们运行internet explorer并输入url:http://localhost/webapp/hello.15seconds,看到的页面如下:



  http处理程式中的对话状态

  维护对话状态是web应用程式执行的最通常的事务。http处理程式也需要访问这些对话状态。但是http处理程式的默认配置是没有激活对话状态的。为了读取和/或写入状态数据,需要http处理程式实现下面的接口之一:

  · irequiressessionstate

  · ireadonlysessionstate.

  当http处理程式需要读写对话数据的时候,他必须实现irequiressessionstate接口。假如他只读取对话数据,实现ireadonlysessionstate接口就能够了。

  这两个接口都是标记接口,并没有包含任何方法。因此,假如您希望激活newhandler处理程式的对话状态,要像下面的代码相同声明newhandler类:

public class newhandler : ihttphandler, irequiressessionstate
  http模块

  http模块是实现了system.web.ihttpmodule接口的.net组件。这些组件通过在某些事件中注册自身,把自己插入asp.net请求处理管道。当这些事件发生的时候,asp.net调用对请求有兴趣的http模块,这样该模块就能处理请求了。

  http模块实现了ihttpmodule接口的下面一些方法:

方法名称 描述 init 这个方法允许http模块向httpapplication 对象中的事件注册自己的事件处理程式。 dispose 这个方法给予http模块在对象被垃圾收集之前执行清理的机会。
  http模块能够向system.web.httpapplication对象暴露的下面一些方法注册:

事件名称 描述 acquirerequeststate 当asp.net运行时准备好接收当前http请求的对话状态的时候引发这个事件。 authenticaterequest 当asp.net 运行时准备验证用户身份的时候引发这个事件。 authorizerequest 当asp.net运行时准备授权用户访问资源的时候引发这个事件。 beginrequest 当asp.net运行时接收到新的http请求的时候引发这个事件。 disposed 当asp.net完成http请求的处理过程时引发这个事件。 endrequest 把响应内容发送到客户端之前引发这个事件。 error 在处理http请求的过程中出现未处理异常的时候引发这个事件。 postrequesthandlerexecute 在http处理程式结束执行的时候引发这个事件。 prerequesthandlerexecute 在asp.net开始执行http请求的处理程式之前引发这个事件。在这个事件之后,asp.net 把该请求转发给适当的http处理程式。 presendrequestcontent 在asp.net把响应内容发送到客户端之前引发这个事件。这个事件允许我们在内容到达客户端之前改变响应内容。我们能够使用这个事件给页面输出添加用于任何页面的内容。例如通用菜单、头信息或脚信息。 presendrequestheaders 在asp.net把http响应头信息发送给客户端之前引发这个事件。在头信息到达客户端之前,这个事件允许我们改变他的内容。我们能够使用这个事件在头信息中添加cookie和自定义数据。 releaserequeststate 当asp.net结束所搜有的请求处理程式执行的时候引发这个事件。 resolverequestcache 我们引发这个事件来决定是否能够使用从输出缓冲返回的内容来结束请求。这依赖于web应用程式的输出缓冲时怎样配置的。 updaterequestcache 当asp.net完成了当前的http请求的处理,并且输出内容已准备好添加给输出缓冲的时候,引发这个事件。这依赖于web应用程式的输出缓冲是如何配置的。
  除了这些事件之外,我们还能够使用四个事件。我们能够通过实现web应用程式的global.asax文档中一些方法来使用这些事件。

  这些事件是:

  · application_onstart

  当第一个请求到达web应用程式的时候引发这个事件。

  · application_onend

  准备终止应用程式之前引发这个事件。

  · session_onstart

  用户对话的第一个请求引发这个事件。

  · session_onend

  放弃对话或对话超期的时候引发这个事件。


  在配置文档中注册http模块

  当我们建立了http模块并把他复制到web应用程式的bin目录或全局部件缓冲(global assembly cache)之后,接下来就应该在web.config或machine.config中注册他了。

  我们能够使用<httpmodules>和<add>节点把http模块添加到web应用程式中。实际上模块都使用<add>节点列举在<httpmodules>和</httpmodules>节点之内了。

  因为配置配置信息是能够继承的,所以子目录从父目录那儿继承配置配置信息。其结果是,子目录可能继承了一些无需的http模块(他们是父配置信息的一部分);因此,我们需要一种删除这些无需的模块的方法。我们能够使用<remove>节点;假如我们希望删除从应用程式继承得到的任何http模块,能够使用<clear>节点。

  下面的代码是添加http模块的一个通用示例:

<httpmodules>
<add type="classname, assemblyname" name="modulename" />
<httpmodules>
  下面的代码是从应用程式中删除http模块的一个通用示例:

<httpmodules>
<remove name="modulename" />
<httpmodules>
  在上面的xml中:

  · type属性用类和部件名称的形式指定了http模块的实际类型。

  · name属性指定了模块的友好名称。其他应用程式能够使用这个名称来识别http模块。

  asp.net运行时如何使用http模块

  asp.net运行时使用http模块实现某些特别的功能。下面的片段来自于machine.config文档,他显示了asp.net运行时安装的http模块:

<httpmodules>
 <add name="outputcache" type="system.web.caching.outputcachemodule"/>
 <add name="session" type="system.web.sessionstate.sessionstatemodule"/>
 <add name="windowsauthentication"
type="system.web.security.windowsauthenticationmodule"/>
 <add name="formsauthentication"
type="system.web.security.formsauthenticationmodule"/>
 <add name="passportauthentication"
type="system.web.security.passportauthenticationmodule"/>
 <add name="urlauthorization"
type="system.web.security.urlauthorizationmodule"/>
 <add name="fileauthorization"
type="system.web.security.fileauthorizationmodule"/>
</httpmodules>
  asp.net使用上面一些http模块来提供一些服务,例如身份验证和授权、对话管理和输出缓冲。由于这些模块都注册在machine.config文档中。


  实现一个提供安全服务的http模块

  现在我们实现一个http模块,他为我们的web应用程式提供安全服务。该http模块基本上是提供一种定制的身份认证服务。他将接收http请求中的身份凭证,并确定该凭证是否有效。假如有效,和用户相关的角色是什么?通过user.identity对象,他把这些角色和访问我们的web应用程式页面的用户的标识关联起来。
下面是该http模块的代码:

using system;
using system.web;
using system.security.principal;

namespace securitymodules
{
 /// class1的总体描述。

 public class customauthenticationmodule : ihttpmodule
 {
  public customauthenticationmodule()
  {
  }
  public void init(httpapplication r_objapplication)
  {
   // 向application 对象注册事件处理程式。
   r_objapplication.authenticaterequest +=
new eventhandler(this.authenticaterequest) ;
  }

  public void dispose()
  {
   // 此处空出,因为我们无需做什么操作。
  }

  private void authenticaterequest(object r_objsender,eventargs r_objeventargs)
  {
   // 鉴别用户的凭证,并找出用户角色。。
   1. httpapplication objapp = (httpapplication) r_objsender ;
   2. httpcontext objcontext = (httpcontext) objapp.context ;
   3. if ( (objapp.request["userid"] == null) ||
   4.  (objapp.request["password"] == null) )
   5.  {
   6.   objcontext.response.write("<h1>credentials not provided</h1>") ;
   7.   objcontext.response.end() ;
   8.  }

   9. string userid = "" ;
   10. userid = objapp.request["userid"].tostring() ;
   11. string password = "" ;
   12. password = objapp.request["password"].tostring() ;
 
   13. string[] strroles ;
   14. strroles = authenticateandgetroles(userid, password) ;
   15. if ((strroles == null) || (strroles.getlength(0) == 0))
   16. {
   17.  objcontext.response.write("<h1>we are sorry but we could not
find this user id and password in our database</h1>") ;
   18.  objapp.completerequest() ;
   19. }

   20. genericidentity objidentity = new genericidentity(userid,
"customauthentication") ;
   21. objcontext.user = new genericprincipal(objidentity, strroles) ;
  }

  private string[] authenticateandgetroles(string r_struserid,string r_strpassword)
  {
   string[] strroles = null ;
   if ((r_struserid.equals("steve")) && (r_strpassword.equals("15seconds")))
   {
    strroles = new string[1] ;
    strroles[0] = "administrator" ;
   }
   else if ((r_struserid.equals("mansoor")) && (r_strpassword.equals("mas")))
   {
    strroles = new string[1] ;
    strroles[0] = "user" ;
   }
   return strroles ;
  }
 }
}
  我们研究一下上面的代码。

  我们是从init函数开始的。这个函数把处理程式的authenticaterequest事件插入application(应用程式)对象的事件处理程式列表中。这将导致引发authenticationrequest事件的时候application调用该方法。

  我们的http模块初始化之后,我们就能够调用他的authenticaterequest方法来鉴别客户端请求。authenticaterequest方法是该安全/身份认证机制的核心。在这个函数中:

  1和2行提取httpapplication和httpcontext对象。3到7行检测是否没有给我们提供了用户id或密码。假如没有提供,就显示错误信息,请求处理过程终止。

  9到12行从httprequest对象中提取用户id和密码。

  14行调用一个叫做authenticateandgetroles的辅助(helper)函数。这个函数主要执行身份验证并决定用户角色。上面的代码采用了硬编码(hard-coded),只允许两个用户使用,但是我们能够扩展这个方法,并添加代码和用户数据库交互操作并检索用户的角色。

  16到19行检测是否有角色和用户关联。假如没有就意味着传递给我们的凭证没有通过验证;因此该凭证是无效的。因此,给客户端发送一个错误信息,并且请求结束了。

  20和21行很重要,因为这两行实际上告诉asp.net http运行时已登录用户的身份。这两行成功执行以后,我们的aspx页面就能够使用user对象访问这些信息了。

  现在我们看一看这种身份验证机制的运行情况。现在我们只允许下面两个用户登录到系统:

  · user id = steve, password = 15seconds, role = administrator
  · user id = mansoor, password = mas, role = user

  注意用户id和密码是大小写敏感的(区分大小写)。

  首先试图不提供凭证登录系统,在ie中输入http://localhost/webapp2/index.aspx将看到下面的消息:


  现在试图使用用户id“steve”和密码“15seconds”登录系统。输入 http://localhost/webapp2/index.aspx?userid=steve&password=15seconds您将看到下面的欢迎消息:


  现在试图使用用户id“mansoor”和秘码“mas”登录系统。输入aspx?userid=mansoor&password=mas">http://localhost/webapp2/index.aspx?userid=mansoor&password=mas您将看到下面的欢迎消息页面:



  现在试图使用错误的用户id和密码组合来登录系统。输入http://localhost/webapp2/index.aspx?userid=mansoor&password=xyz您将看到下面的错误消息:


  这表明我们的安全模块在起作用了。您能够通过在authenticateandgetroles方法中使用数据库访问代码来扩展该安全模块。

  要使任何的部分都起作用,我们必须对web.config文档进行一些修改。首先,由于我们要使用自己的身份验证,因此无需其他的身份验证机制。为了达到这个目的,改变webapp2的web.config文档中的<authentication>节点,如下所示:

<authentication mode="none"/>
  类似地,不允许匿名用户访问我们的web站点。给web.config文档添加下面的语句:

<authorization>
 <deny users="?"/>
</authorization>
  用于至少能够匿名访问用于提供凭证的文档。在web.config文档中使用下面的配置配置信息把index.aspx作为唯一能够匿名访问的文档:

<location path="index.aspx">
 <system.web>
  <authorization>
   <allow users="*"/>
  </authorization>
 </system.web>
</location>
  结论

  您可能已意识到有了http处理程式和模块后,asp.net已给研发者提供了强大的能量。把您自己的组件插入asp.net请求处理管道,享受他的长处吧。

  作为练习,您应该进一步改进程式,使示例身份验证模块更加灵活,并能根据用户的需要进行调整