securely implement request processing, filtering and content redirection with HTTP pipeline in ASP.NET

HTTP Pipelines

Securely Implement Request Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET


 and  Keith Brown
这篇文章假设您已经对ASP.NET 和C#有一定的了解。
 
概述:

ASP.NET是用于进行服务器端HTTP编程的框架,这个框架非常灵活,并且具有很好的可扩展性。有些人简单的认为ASP.NET实际上就是一系列的页面处理,其实在ASP.NET的页面模型下还有一个低层次的基础模型存在。上层的页面处理需要依赖于这些底层app, module, handler对象的管道(pipeline)处理。只有理解了这个管道是如何运作的你才可以使你的处理过程更加有效,使你的网络应用程序具有更好的安全性。这篇文章首先介绍了这个底层管道的结构并通过例子展示怎样在一个基于ASP.NET的网络应用程序中使用它。


Contents

许多人都认为ASP.NET仅仅就是由一些页面组成的,ASP.NET平台通过一定的逻辑处理将代码最终转化成HTML源代码然后将HTML源代码返回至请求的浏览器将页面信息展现出来。但是对于HTTP管道来说这仅仅是其中一个主要应用。HTTP管道可以看作所有服务器端HTTP处理的通用框架,无论是ASP.NET页面还是网路服务都需要其进行处理。作为一名基于ASP.NET平台的开发者,你有必要知道这样一个管道模型是如何工作的。这篇文章就是来解释HTTP管道是如何处理来自客户端的请求的。


管道对象模型:

在命名空间System.Web中定义的类型在处理HTTP请求的时候采用的是管道模式(pipeline model,即在整个处理过程由多个子过程共同完成,这些子过程对输入数据进行加工,然后将加工后的数据进行输出,作为下一个处理子过程的输入数据。)如图1所示,这里展示了管道模型的结构图。一个HTTP请求首先被传递到一个HttpRuntime类型实例中。然后HttpRuntime对象检查这个HTTP请求的内容并确定这个请求是指向哪一个网络应用程序(Web Application)(如果从管道模型的角度看,IIS中一个虚拟目录就是一个网络应用程序。)接着HttpRuntime对象通过HttpApplicationFactory工厂类来找到或者创建一个HttpApplication对象实例,这个HttpApplication对象实例将用来具体处理这个HttpRequest请求。一个HttpApplication对象包含了Http Module对象集合。Http Module类型是指一些实现了IHttpModule接口的类。这些Http Modules对象在管道中就充当数据加工的各个子环节,在这些Http Modules中Http请求的数据将被进行处理(如:检查数据合法性、修改数据、删除数据等等)。在请求数据被所有Http Module处理后,紧接着HttpApplication对象通过Http Handler工厂类找到或者是创建一个Http handler对象,Http handler表示服务器对Http请求的最后一个环节,在这个环节最后将产生发送到客户端的响应数据。Http handler类型和Http handler工厂类分别实现了IHttpHandler和IHttpHandlerFactory的接口。

 

Figure 1 HTTP Pipeline Processing
Figure 1 HTTP Pipeline Processing

一个HttpApplication类型实例,其包含的Http Modules和Http Handlers一次只能用于处理一个Http 请求(Http Request)。如果在某个时间段内由多个请求同时请求同一个网络应用程序(Web Application),那么将有多个HttpApplication类型实例被创建,而每个HttpApplication实例对象将用于处理一个Http请求(Http Request)。为了减少创建和销毁(Dispose)HttpApplication实例对象所带来的性能消耗,HttpApplicationFactory和HttpHandlerFactory类型对象将负责对HttpApplication和Http Handler对象进行缓存以提高整个应用程序的效率。

在管道模型中处理的原料是一个叫做HttpContext类型的对象,这个HttpContext用于表示每个请求/响应对。HttpContext实例对象首先被传送到HttpApplication对象(用HttpApplication中包含的一系列的Http Modules进行处理),最后将处理完成的HttpContext对象传递到Http Handler中。HttpContext中包含了Http Request的相关信息和Http Response服务器响应信息。这些信息被分别存储在HttpContext中包含的HttpRequest和HttpResponse对象中。除了包含请求和响应的相关信息外,希望还包含了其他一些安全相关的属性以及请求(call), 会话(session),和应用程序的状态信息。Figure 2 展示了HttpContext类型所包含的一些常用的信息。

请记住,ASP.NET平台的HTTP 管道模型(HTTP pipeline)是可以灵活的扩展的。你可以通过实现(implement)一些自定义的HTTP Modules, HTTP Handlers以及Http Handler Factory类型来实现一些自定义的处理效果。当然你也可以扩展(extend)HttpApplication类型的功能。

Back to Contents Back to top

管道处理模型

ASP.NET管道模型以来于IIS(Internet Information Services)网络服务器。IIS用于接收来此客户端的请求,进而处理这些客户端的请求。当IIS接收到了来自客户端的一个Http 请求后,IIS首先检查请求资源的扩展名(可以从请求资源的URL中得到),如果这个资源扩展名有一段相应的执行代码,那么IIS就通过调用并执行这段代码来处理这个HTTP请求。资源扩展名和对应执行代码的映射关系存储在IIS的元数据(metabase)中。当ASP.NET被安装以后,它所带的aspnet_isapi.dll映射文件中就包含了一些标准的ASP.NET映射文件及其对应执行代码的映射关系。

当IIS接收到一个来自客户端的HTTP请求后,IIS调用aspnet_isapi.dll中的代码,这些代码将根据请求的不同将请求发送到HTTP处理管道模型中。aspnet_isapi.dll通过一个特定的管道(named pipe)将这个客户端请求从IIS Service运行的进程(inetinfo.exe)推到ASP.NET的一个子进程中(worker process, aspnet_wp.exe)【如果使用的是.net服务器,由于整合了IIS6.0,系统允许直接将请求从系统进程中传递到ASP.NET的一个子进程中,而不是先经过IIS的inetinfo.exe进程。】然后ASP.NET子进程通过创建或者使用已经存在的HttpRuntime实例对象来处理这个来自客户端的请求,图3表达了这段文字描述的结构。

Figure 3 ASP.NET Pipeline Architecture
Figure 3 ASP.NET Pipeline Architecture

 

上面提到了,HTTP管道在处理Http请求的时候是在ASP.NET的aspnet_wp.exe子进程中进行的。一般情况下,一次仅仅只有一个aspnet_wp.exe在使用(当然如果您的服务器具有多个CPU,那么你可以通过设置管道运行模式,在多个CPU上并行的处理多个Http请求)。这种运行方式对旧版的IIS来说是一个显著的变化,在旧版的IIS中通常为每个网络应用程序创建一个独立的aspnet_wp.exe进程,这样可以将各个不同的网络应用程序进行隔离。在目前使用一个aspnet_wp.exe的情况下,使用一个叫做应用程序域(AppDomains)的概念来将各个运行的网络应用程序相互隔离,你可以将应用程序域想象成在aspnet_wp.exe内部轻量级的进程(注意:这里不能将应用程序域想象成县线程,因为各个应用程序域之间应该还是相互独立的。),IIS将所有请求同一个虚拟目录客户端请求都发送到同一个应用程序域中。还句话说,每个虚拟目录都被看成是相互独立的网络应用程序,这个对于旧版的IIS来说也是一个显著的变化,在旧版的IIS中不同的虚拟目录可以是某一个网络应用程序的组成部分。

ASP.NET支持对aspnet_wp.exe进程回收,这个回收利用机制是基于一系列的参数和标准的,包括aspnet_wp.exe形成的时间、空闲时间、aspnet_wp.exe处理的请求数,在队列中等待的请求数,消耗的物理内存数等等。这些限制的参数设置可以在machine.config的processModel配置节中找到文件中找到。如果一个aspnet_wp.exe进程超出了这些限制中的一个,那么aspnet_isapi.dll将执行代码启用一个新的aspnet_wp.exe进程,然后后续的请求将在新的aspnet_wp.exe进程中进行。老的aspnet_wp.exe进程将在处理完所有挂起的请求后被终止。这种工作进程的回收策略通过终止进程的方式来防止这些工作进程由于资源泄漏或者其他运行时的原因造成的低效。

Back to Contents Back to top

HTTP句柄(HTTP Handlers)

HTTP句柄是指那些实现了IHttpHandler接口的类,下面就是对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; }
}

HTTP句柄同样可以实现IHttpAsyncHandler接口以支持异步调用方式。

其中IHttpHandler接口中定义的ProcessRequest方法在HttpApplication对象需要处理一个Http请求的时候被调用,并用来生成一个Http响应流。而IsReuseable属性用来确定这个Http Handler是否可以被重复利用。

下图的代码简单的实现了一个可以重复使用的Http Handler, 这个Http Handler用来以XML的格式来返回服务器的当前时间。这里应当引起注意的式使用HttpContext对象的Response属性来设置Http响应流类型为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; } }
  }

}


完成一个Http Handler的功能后,还需要将这个Http Hanlder进行部署。部署的步骤分为3步:

1、首先你需要将编译后的代码放置在一个ASP.NET工作进程可以找到的路径下;一般地,这意味着你需要将.NET程序集(通常指DLL文件)放置在你对应的网络应用程序(Web Applicaiton)的bin目录下或者放置在全局程序集路径下(GAC)。

2、然后你需要告诉Http管道模型当有Http请求到来时需要执行这个Http Handler;实现这个联系的方法时通过添加在web.config中添加<httpHandler>配置节的配置信息来实现的。

   
   
< configuration >
 
< system .web >
  
< httpHandlers >
   
< add  verb ="GET"  path ="*.time"
     type
="Pipeline.TimeHandler, 
     Pipeline"

   
/>
  
</ httpHandlers >
 
</ system.web >
</ configuration >

上面的配置信息将被认为是对.net全局配置文件(machine.config)的补充或者修改。在上面举的具体例子中,新添的配置节告诉ASP.NET Http 管道模型通过Get方式来处理来自Http请求.time为后缀名的文件时需要调用名称为Pipeline.TimeHandler的Http Handler。

3、最后你需要告诉IIS对于对以.time为后缀名的文件请求将怎样进行处理。这意味着需要在aspnet_isapi.dll的元文件中增加对于.time文件和对应处理方法的元数据描述。最简单的办法便是通过IIS管理控制台向其进行添加,如下图所示。

Figure 5 Adding a File Mapping
Figure 5 Adding a File Mapping

除了通过实现IHttpHandler接口来定制自定义的HttpHandler对象,你同样可以创建自己的Http Handler工厂类。一个Http Handler工厂是指一个实现了IHttpHandlerFactory接口的类型。布署自定义Http Handler工厂的方法和部署Handler的方法一样,其中唯一不同的是配置节的名称不同。如果你布署了一个自定义的Http Handler而没有为其指定一个自定义的Http Handler Factory,那么管道将建立一个默认的Http Handler工厂实例对象,即:HandlerFactoryWrapper对象来进行处理。


标准的Http Handler

在ASP.NET模型的上层技术中,如页面(page)和网络服务(web services)都是直接建立在Http Handlers之上的。通过查看.NET的全局配置文件(machine.config)可以看到名称为<httpHandlers>的配置节:

   
   
< 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 >

的文件对应到SimpleHandlerFactory类型,SimpleHandlerFactory可以根据.ashx文件的源代码来生成一个实现IHttpHandler接口的IHttpHandler。生成的Http Handler实例对象可以直接被ASP.NET的Http管道(PipeLine)所使用。

<% @ 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; } }
  }

}


上图展示了我们前面通过.ashx文件重新实现TimeHandler的代码。其中@WebHandler指令将编译后将生成的Http Handler的信息添加告诉SimpleHandlerFactory类型。通过.ashx文件来实现自定义Http Handler的好处主要是其不需要进行前面所说的布署,你唯一要做的就是将.ashx文件拷贝到你的虚拟目录下就可以了(省略了创建或者修改web.config文件和更新IIS中的映射关系的步骤)。

第二个<httpHandlers>配置节将.aspx为后缀的文件映射到了PageHandlerFactory类型,PageHandlerFactory类型知道怎样将一个.aspx文件编译成一个继承自System.Web.UI.Page的类型,并且创建其实例。由于System.Web.UI.Page类型实现了IHttpHandler接口,所以生成的实例可以直接被应用于Http管道(Pipeline)。

第三个配置节将.asmx文件对应到WebServiceHandlerFactory类型,WebServiceHandlerFactory类型实例将编译并且实例化.asmx文件中的代码,然后将根据.asmx文件代码生成的类型实例包装成一个标准的Http Handler(没人是生成为SyncSessionHandler类型的Http Handler),这种类型的Http Handler可以通过反射(reflection)将SOAP信息转化为方法的调用。然后生成的Http Handler也可以直接被应用到Http管道中。

这里需要指出的是PageHandlerFactory, WebServiceHandlerFactory和SimpleHandlerFactory并不是每次请求都会重新编译.aspx,.asmx和.ashx文件,而是将编译的结果缓存到你安装.NET目录下的名称为“Temporary ASP.NET Files ”的子文件夹下面(如果你同时安装了多个版本的.NET Framework,那么每个版本的.NET Framework将存在一个对应的Temporary ASP.NET Files 文件夹)。这些代码仅仅只有在内容出现了改变以后才会被重新编译缓存。


Http 模块(Http Modules)

Http Hanlder对HttpContext的处理是Http Pipeline处理的最后一个环节,在这里HttpRequest将终止,Http Handler根据处理结果得到Http响应(Http Response)。Http 模块(Http Modules)可以看成Http 管道中处理Http Request数据的一系列的过滤器,当Http Request数据流过Http管道的时候,各个过滤器(Http Modules)将对其中的数据进行处理(如:检查数据的合法性、修改数据内容等)。Http Modules主要是用来处理安全和会话管理等方面的事情。

简单的说Http Modules可以看成一系列实现了IHttpModule接口的类,以下就是IHttpModule接口定义:

   
   
interface  IHttpModule
{
  
// called to attach module to app events
  void Init(HttpApplication app);
  
// called to clean up
  void Dispose()
}

其中接口中定义的Init方法将在Http Module被HttpApplication对象第一次创建的时候被调用。在这个方法中可以将这个Http Module和HttpApplication中的多个事件进行绑定,以便进行处理。
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());
    }

  }

}

上图展示了一个简单的Http Module的实现,这个Http Module将在HttpApplication中的BeginRequest和EndRequest事件中被触发,然后计算BeginRequest和EndRequest之间所用的时间。首先我们通过+=将OnBeginRequest和OnEndRequest函数添加注册到HttpApplication中的BeginRequest和EndRequest事件。在BeginRequest事件被触发后,我们通过DateTime.Now属性得到当前时间,然后在EndRequest事件被触发以后记录当前事件,然后通过两个时间相减得到消耗的时间。由于触发事件的是HttpApplication对象,所以这里的 object o就是对HttpApplication对象的引用,所以可以通过强制转换后得到HttpApplication中HttpContext属性从而修改Response的信息内容。

一个Http Module的布署过程分为两步,首先你必须将编译过的Http Module代码(通常是DLL文件)放置在你Web应用程序的bin文件夹下,或者是放置在GAC文件夹下。这样ASP.NET的工作进程(worker process, aspnet_wp.exe, wp 是 work process 的简写形式)可以找到这个Http Modules。

然后你必须告诉Http管道每当有向指定的应用程序请求时,都要创建你定制的Http Module。这是通过在配置文件中(web.config, machine.config)中添加<httpModules>配置节来实现的。

   
   
< configuration >
 
< system .web >
  
< httpModules >
   
< add
    
name ="Elapsed"
    type
="Pipeline.ElapsedTimeModule, Pipeline"
   
/>
  
</ httpModules >
 
</ system.web >
</ configuration >
在上面这个例子中,web.config文件告诉ASP.NET的Http管道,如果有请求来请求当前这个网络应用程序,那么名称为“Pipeline.ElapsedTimeModule”的Http Module都将被创建。

ASP.NET管道事件模型

在前面的ElaspedTimeModule的例子中我们实现了两个事件的响应函数,分别是OnBeginRequest和OnEndRequest两个事件。其实在整个管道处理中,存在很多事件,刚刚提到的两个事件仅仅是众多事件中的两个。以下列举的是整个管道处理的完整事件列表:

EventWhen It's Called
BeginRequestBefore request processing starts
AuthenticateRequestTo authenticate client
AuthorizeRequestTo perform access check
ResolveRequestCacheTo get response from cache
AcquireRequestStateTo load session state
PreRequestHandlerExecuteBefore request sent to handler
PostRequestHandlerExecuteAfter request sent to handler
ReleaseRequestStateTo store session state
UpdateRequestCacheTo update response cache
EndRequestAfter processing ends
PreSendRequestHeadersBefore buffered response headers sent
PreSendRequestContentBefore buffered response body sent

这里需要注意的是由Http Application产生的HTTP Handler实例(HTTP Handler用于在处理管道的最后对请求信息进行处理)是在ResolveRequestCache事件和AcquireRequestState事件之间创建的。所有有关请求用户的回话状态(Session State)都将在AcquireRequestState事件的处理过程中得到。当HTTP Handler被实例化以后,它将在PreRequestHandlerExecute和PostRequestHandlerExecute事件事件之间被调用。(实际上Pre和Post就是针对HTTP Handler被调用的前后来命名的)

HttpApplication定义的这些事件使用多点代理(multicast delegates)来实现,所以每个事件可以挂接多个事件的响应函数。但是处于对执行效率的考虑,每个事件上挂接的处理模块数应该尽量的少。由于每个HttpApplication实例和由其创建的Http Modules仅仅用来处理其中一个具体的请,(so individual HTTP module objects can store any per-request state they need across multiple events.?? 不懂)

在某些情况下,一个Http Module(Http 请求处理模块)可能希望影响整个管道的处理流程,而不是让管道按照一个既定的执行顺序进行处理。如:一个模块用来进行用户的身份校验,那么如果它发现进行处理的Http Request请求没有包含用于表明用户身份的Cookie信息时,页面需要自动跳转到系统的登录界面,而不是进行继续进行用户数据的处理。HttpApplication对象中存在一个CompleteRequest方法,当任何一个Http处理管道的事件处理函数调用了HttpApplication.CompleteRequest方法,那么正常的管道处理流程将被中断。而调用HttpApplication.CompleteRequest方法的事件处理模块同时需要生成正确的Http响应信息。(Http Response Message)

以下代码展示提供了一个使用HttpApplication.CompleteRequest的例子。

using  System;
using  System.Web;

namespace  Pipeline
{
  
public class EnableWebServicesModule : 
               IHttpModule
  
{
    
// field for turning behavior on and off
    public static bool enabled = true;

    
public void Init(HttpApplication app)
    
{
      
// register event handler
      app.BeginRequest +=
       
new EventHandler(this.OnBeginRequest);
    }

    
public void Dispose() {}

    
public void OnBeginRequest(object obj, EventArgs ea)
    
{
      
// if web services are enabled, let
      
// request proceed through pipeline
      lock(typeof(EnableWebServicesModule))
      
{
        
if (enabled) return;
      }


      
// check to see if request is a SOAP
      
// message by looking for SOAPAction
      HttpApplication app = (HttpApplication) obj;
      HttpContext ctx 
= app.Context;
      
string s = ctx.Request.Headers["SOAPAction"];
      
if (s == nullreturn;

      
// if web services are disabled and
      
// request is SOAP message, abort processing
      app.CompleteRequest();
      ctx.Response.StatusCode 
= 403;
      ctx.Response.StatusDescription 
= "Forbidden";
      ctx.Response.ContentType 
= "text/plain";
      ctx.Response.Write(
"No!");
  }

}


在例子中Http Module通过调用CompleteRequest方法来阻止对Web Service方法的正常调用。根据SOAP说明书中的描述,如果HTTP中包含的是SOAP信息,那么其对应的Http头中一定要存在一个名称为SOAPAction的自定义头信息。在例子中EnableWebServiceModule类型的OnBeginRequest事件处理函数中检查如果Http头中存在SOAPAction自定义头信息(即:表示Http请求中包含的是SOAP信息), 并且当前设置“能否调用Web Service”的方法为False(即:不可调用Web Service方法)那么就退出正常的处理流程(在例子中是在响应信息中添加“禁止调用Web Service服务”的提示信息)。

using  System;
using  System.Web;

namespace  Pipeline
{
  
public class EnableWebServicesHandler :
               IHttpHandler
  
{
    
public void ProcessRequest(HttpContext ctx)
    
{
      
// toggle module's enabled field and
      
// return new value as HTML
      lock(typeof(EnableWebServicesModule))
      
{
        EnableWebServicesModule.enabled 
=  
              
!EnableWebServicesModule.enabled;
        ctx.Response.ContentType 
= "text/html";
        ctx.Response.Write(
"<h1>Web Services " +
            (EnableWebServicesModule.enabled 
?
             
"Enabled" : "Disabled"+ "</h1>");
      }

    }

    
public bool IsReusable {get return true; }}
  }

}

以上代码展示了在Http Handler的ProcessRequest方法中更改EnableWebServicesHandler的Enabled字段。ProcessRequest在每个请求的处理最后被调用。

如果我们假设以上的Http Module和Http Handler的代码最后都被编译为Pipeline.dll文件,那么以下代码展示了怎样对这些过滤器(处理器)进行布署。

< configuration >
 
< system .web >
  
< httpModules >
   
< add  name ="WebServicesEnabledModule"
    type
="Pipeline.EnableWebServicesModule, Pipeline"
   
/>
  
</ httpModules >
  
< httpHandlers >
   
< add  verb ="*"  path ="toggle.switch"
    type
="Pipeline.EnableWebServicesHandler, Pipeline"
  </httpHandlers
>
 
</ system.web >
</ configuration >

当然对于Http Handler来说,在IIS中配置对应的原数据(meta data)的步骤也是必须的,这里就不再进行赘述。

这里针对Http Module 和 HttpApplication.CompleteRequest方法还需要特别指出的是,当管道中的一个方法调用了HttpApplication.CompleteRequest方法,从而导致正常的管道处理流程被中断,但是EndRequest事件及其后续事件(PreSendRequestHeaders,PreSendRequestContent)对应的处理函数已然会被执行,所以如果在管道处理流程中断前使用了任何资源都可以在这些后续事件处理函数中被释放掉,如:在管道BeginRequest事件处理模块中使用了一个锁资源(lock)来保证对临界资源访问和操作的正确性,在EndRequest的事件处理函数中可以对这个锁资源进行释放。这里需要再次强调的是,无论在何种情况下EndRequest事件及其处理函数都将被执行。


HTTP Applications

正如上面我们提到的,在ASP.NET HTTP管道将每个虚拟目录都看成一个Http 应用(Http Application)。当一个对具体Http 应用的请求到达时,HttpRuntime对象通过调用HttpApplicatinFactory对象来创建或者从缓冲池中选择一个HttpApplication对象,然后这个HttpApplication对象就用于也仅仅用于处理这个请求。出于对效率的考虑,每一个Http 应用(Http Application)都存在一个对应的HttpApplication缓冲池。

如果有需求,那么你可以自定义针对你自己的Http 应用(Http Application, 也就是IIS中的虚拟目录)来定制Http Application的行为。这个过程是通过编写Http 应用中的global.asax文件来实现的。如果ASP.NET Http 管道发现对应的虚拟目录中存在global.asax文件,那么ASP.NET Http管道将global.asax中的代码编译成一个继承自HttpApplication类的类,然后ASP.NET Http管道将实例化这个继承自HttpApplication的子类,并由此子类来处理针对此虚拟目录的请求。 

global.asax 最有用的用途依然是对HttpApplication中暴露的事件进行处理。这里处理的事件可以归为两类:其中一类是HttpApplication自身的事件列表,就是上面我们列举的那些事件。正如前面我们说的,我们可以通过Http Module来对这些事件进行处理。但是在有些时候通过实现Http Module方法来处理这些管道事件并不是最便捷的方法,特别是当特殊处理是针对某个特定Http 应用的时候。

例如,在global.asax文件中你也可以通过如下实现来处理HttpApplication.BeginRequest

     
     
<% @ import namespace="System.Web"  %>

<!--  this code will be added to a new 
     HttpApplication-derived class 
-->
< script  language ="C#"  runat =server >

public 
void Application_BeginRequest(
            object obj, EventArgs ea)
{
  string s 
= Context.Request.Headers["SOAPAction"];
  Context.Items[
"IsSOAP"= (s != null);
}


</ script >
在上面这个例子中,Applicaiton_BeginRequest事件处理函数检查当前的Http Request是否是一个Web Service调用(通过检查HttpRequest.Headers["SOAPAction"]是否存在),然后把判断的结果记录在当前请求上下文(HttpContext)的Items属性中(Items["IsSOAP"])。其中HttpContext.Items属性使用来存储当前请求的一些状态和信息值的属性,这些信息可以在后续的Http处理的Http Modules和Http Handler中使用。HttpContext.Items中信息将在请求处理完毕后被清空。当然以上的功能你也可以在Http Modules中实现,但是如果仅仅是针对一个特定的Http 应用(Http Application)那么在Global.asax中实现将更加方便。(你可能会觉得在子类型中处理父类型提供的方法不是很常见的做法,但是Global.asax中就是这样实现的,而且非常方便。)

Two additional application-level events are not listed in Figure 8 and are not made available to HTTP modules in the normal way, namely, Application_OnStart and Application_OnEnd. These events are familiar to classic ASP programmers. They are called when an application is first accessed and when it shuts down, respectively. Here is a simple example:

<%@ import namespace="System.Web" %>

<script language="C#" runat=server>

public void Application_OnStart()
{
  ... // set up application here
}

public void Application_OnEnd()
{
  ... // clean up application here
}

</script>

 

The other category of events that an HttpApplication-derived class might handle is events fired by HTTP modules. In fact, this is how the pipeline implements the classic ASP Session_OnStart and Session_OnEnd events, both of which are fired by the SessionStateModule class.

Consider the EnableWebServicesModule presented earlier that conditionally rejects Web Service invocations based on the state of a static field. When it rejects a request, it does so brusquely, with a hardcoded, somewhat curt message. It might be better if the module allowed the application it is being used with to tailor the message for its own purposes. One way to do this is to have the HTTP module fire an event when a Web Service request is rejected. Figure 11 shows a new version of the EnableWebServicesModule that fires a Rejection event when a Web Service request is rejected. The modified implementation of the module's OnBeginRequest event handler checks to see if there are any handlers registered for the Rejection event by comparing the property to null. If one is registered, the module fires the event and expects the handler to produce an appropriate HTTP response message. If no handlers are registered, the module generates its own HTTP response message with the same abrupt tone.

An application can handle the events fired by a module simply by implementing a method with the correct signature. The syntax is based on the name assigned to the HTTP module in the Web.config file when the module was deployed and the name of the event the module fires. In the previous example, the module was given the name EnableWebServicesModule (which also happens to be its class name, but that is just coincidence) and the event is called Rejection. Based on that, the signature for the HttpApplication subclass's handler for the event is:

public void EnableWebServicesModule_Rejection(
            object o, EventsArgs ea);
Here is an implementation:
<%@ import namespace="System.Web" %>

<script language="C#" runat=server>

public void EnableWebServicesModule_Rejection(
            object o, EventArgs ea)
{
  Context.Response.StatusCode = 403;
  ctx.Response.StatusDescription = "Forbidden";
  ctx.Response.ContentType = "text/plain";
  ctx.Response.Write("Unfortunately, web " +
        "services are not available now, " +
        "please try your request again");
}

</script>
The pipeline plumbing knows how to wire up this event handler based on its name. Now when the HTTP module rejects Web Service invocations, it will fire the Rejection event and the application will have a chance to generate a friendlier HTTP response message. The entire new architecture, including the handler for controlling the module's behavior, is shown in Figure 12.

 

Figure 12 EnableWebServicesModule Architecture
Figure 12 EnableWebServicesModule Architecture

Security in the Pipeline

One of the most common uses of HttpModules is to implement security features such as authentication and authorization, a healthy dose of which can be layered on top of an application quite transparently. In fact, take a look at the default list of HttpModules installed for all Web applications by machine.config (We've omitted the type names for brevity):

<httpModules>
  <add name="OutputCache"            type="..."/>
  <add name="Session"                type="..."/>
  <add name="WindowsAuthentication"  type="..."/>
  <add name="FormsAuthentication"    type="..."/>
  <add name="PassportAuthentication" type="..."/>
  <add name="UrlAuthorization"       type="..."/>
  <add name="FileAuthorization"      type="..."/>
</httpModules>

 

Aside from the output caching and session state management modules, these modules are there to help implement security. Also note that the order of the modules is important. Authentication answers the question "Who are you?", while authorization answers the question "Are you allowed to do this?" Clearly authentication must happen before authorization, thus the order of the modules shown previously.

The three authentication modules correspond to the three options in web.config for performing authentication:

<authentication mode='None|Windows|Forms|Passport'>
By selecting a mode other than None, you enable the corresponding authentication module to do its work. The job of these modules is to perform an authentication handshake with the client and possibly a trusted authority such as passport.com. Once authenticated, these modules create an implementation of IIdentity and IPrincipal that can be used by the authorization modules downstream to determine if the request should be granted or denied. This information is hung on the HttpContext.User property. The HttpHandler at the end of the pipeline can also use this information.

 

To illustrate, imagine your web.config file was written this way:

<configuration>
  <web.config>
    <authentication mode='Forms'/>
    <authorization>
      <deny users='?'/>
      <allow roles='Managers, Staff'/>
      <deny users='*'/>
    </authorization>
  </web.config>
</configuration>
Now imagine that a user tries to access the Web application, but is not currently logged on via Forms authentication and thus is considered anonymous. The FormsAuthentication module first processes the request and notices that the client has not sent the special cookie that represents a successful prior login. Thus it constructs an IIdentity object that indicates the user is anonymous and an IPrincipal object that binds an empty set of roles to that identity, attaching this to the HttpContext.User property. When the UrlAuthorization module gets the request, it notes that anonymous access to the directory has been denied in web.config. The <deny users='?'/> tag in web.config represents denial of any anonymous requests. The module checks the user associated with the current context via the HttpContext.User property, sees that it's anonymous, and therefore completes the request and indicates that access is forbidden. Remember though, we're not done yet. The FormsAuthenticationModule now gets to see this forbidden request being sent back to the client, notes that the user is not authenticated, and therefore changes the response into a redirect to the default login page, login.aspx.

 

Assuming the user submits valid credentials to the login page, the login page handler calls FormsAuthentication.RedirectFromLoginPage, which redirects the user back to the page she was after in the first place while sending her an encrypted cookie containing the authenticated user's name. When the redirection causes the client to request the original page again, the FormsAuthentication module decrypts and validates the cookie, then constructs an IIdentity for an authenticated user along with her name. This identity is bound with an empty set of roles and attached to HttpContext.User.

We didn't mention this before, but after setting HttpContext.User, the FormsAuthentication module causes the AuthenticateRequest event to fire. If you've implemented a handler for this event in your global.asax file, you'll now have a chance to take the IIdentity produced by the FormsAuthentication module and bind it to a set of application-defined roles. The code in Figure 13 shows an example of this.

Now that the FormsAuthentication module is finished processing the request, the UrlAuthorization module has its turn. It notes that the request is authenticated, so the first line in the <authorization> section of web.config is satisfied. Now it looks to see if the principal is in either the Managers or Staff role. If so, the request will be allowed; otherwise, the last line of the <authorization> section will cause the request to be denied. In the <authorization> section of web.config, the wildcard character (?) indicates unauthenticated requests; the star character (*) indicates all requests.

This example illustrates how the flexibility of the HTTP pipeline makes it possible to layer security—specifically authentication and authorization—onto many different Web applications without much effort.


Conclusion

This article introduced the ASP.NET pipeline, a very flexible infrastructure for server-side HTTP development. The HTTP pipeline integrates with IIS and provides a rich programming model based on applications, modules, and handlers—all of which you can implement if you want. The HTTP pipeline is a large piece of plumbing and there are many important aspects to it that we did not have space to mention, including support for state management, which is a feature-length topic in its own right. Hopefully this article will help you better understand how the pipeline works and how you can use it in your HTTP-based .NET applications.



For related articles see:
The ASP Column: ASP.NET Connection Model and Writing Custom HTTP Handler/Response Objects
For background information see:
HttpApplication Members
HttpApplication Methods

Tim Ewaldis a Program Manager for XML Web Services at Microsoft, working on Web Service specifications, APIs, and distributing information to developers. He is the author of Transactional COM+: Building Scalable Applications (Addison-Wesley, 2001). Reach Tim at tewald@microsoft.com.

Keith Brownworks at DevelopMentor researching, writing, teaching, and promoting an awareness of security among programmers. Keith authored Programming Windows Security (Addison-Wesley, 2000). He coauthored Effective COM, and is currently working on a .NET security book. He can be reached at http://www.develop.com/kbrown/.
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值