首页 > 代码库 > MVC之前的那点事儿系列(5):HttpPipeline详细分析(下)(转载)

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下)(转载)

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下)

文章内容

接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码:

public class Test : IHttpModule{    public void Init(HttpApplication context)    {        context.BeginRequest += new EventHandler(context_BeginRequest);        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);    }    void context_AuthenticateRequest(object sender, EventArgs e)    {        throw new NotImplementedException();    }    void context_BeginRequest(object sender, EventArgs e)    {        throw new NotImplementedException();    }}

然后添加的代码,在Pipeline里执行的时候就会把这些事件给执行了,那么如何执行并且按照什么顺序执行的呢? 在了解这些之前,我们先看看这些事件是如何在HttpApplication里暴露出来了,添加事件存放在何处的呢?阅读HttpApplication的源码,我们可以看到,所有的事件都是按照如下的形式暴露的,选择其中两个看一下:

/// <devdoc><para>[To be supplied.]</para></devdoc> public event EventHandler BeginRequest {    add { AddSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }    remove { RemoveSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }}  /// <devdoc><para>[To be supplied.]</para></devdoc> public event EventHandler AuthenticateRequest {    add { AddSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); }     remove { RemoveSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); }}

可以发现,所有的事件都是调用AddSyncEventHookup方法添加进去的,其中第一个参数是以Event+事件名称的值,这个值是如何得来的,我们找到声明的代码:

private static readonly object EventDisposed = new object();private static readonly object EventErrorRecorded = new object();private static readonly object EventPreSendRequestHeaders = new object(); private static readonly object EventPreSendRequestContent = new object(); private static readonly object EventBeginRequest = new object(); private static readonly object EventAuthenticateRequest = new object();private static readonly object EventDefaultAuthentication = new object(); private static readonly object EventPostAuthenticateRequest = new object();private static readonly object EventAuthorizeRequest = new object();private static readonly object EventPostAuthorizeRequest = new object();private static readonly object EventResolveRequestCache = new object(); private static readonly object EventPostResolveRequestCache = new object();private static readonly object EventMapRequestHandler = new object(); private static readonly object EventPostMapRequestHandler = new object(); private static readonly object EventAcquireRequestState = new object();private static readonly object EventPostAcquireRequestState = new object(); private static readonly object EventPreRequestHandlerExecute = new object();private static readonly object EventPostRequestHandlerExecute = new object();private static readonly object EventReleaseRequestState = new object();private static readonly object EventPostReleaseRequestState = new object(); private static readonly object EventUpdateRequestCache = new object();private static readonly object EventPostUpdateRequestCache = new object(); private static readonly object EventLogRequest = new object(); private static readonly object EventPostLogRequest = new object();private static readonly object EventEndRequest = new object(); 

再结合add和remove方法,可以大胆猜想,这些值应该是作为key值用的,我们先看完第2个参数,再来验证我们的猜想,第2个参数是枚举类型RequestNotification,这里我们再猜想一下,所有的事件都应该放在统一的地方,然后用这个枚举来区分。让我们先看看这个枚举类的代码:

[Flags]public enum RequestNotification{    BeginRequest = 1,    AuthenticateRequest = 2,    AuthorizeRequest = 4,    ResolveRequestCache = 8,    MapRequestHandler = 16,    AcquireRequestState = 32,    PreExecuteRequestHandler = 64,    ExecuteRequestHandler = 128,    ReleaseRequestState = 256,    UpdateRequestCache = 512,    LogRequest = 1024,    EndRequest = 2048,    SendResponse = 536870912,}

发现什么了没有?虽然使用了Flags标记来记录以便进行异或查询,但是这里的枚举类型好像少了一些吧,仔细对照代码发现所有以Post开头的事件都没出现在这个枚举类里,为什么呢?那这些事件是如何声明的?回到HttpApplication类来继续查看代码,

/// <devdoc><para>[To be supplied.]</para></devdoc>public event EventHandler PostAuthenticateRequest {    add { AddSyncEventHookup(EventPostAuthenticateRequest, value, RequestNotification.AuthenticateRequest, true); }     remove { RemoveSyncEventHookup(EventPostAuthenticateRequest, value, RequestNotification.AuthenticateRequest, true); }} 

突然发现,这个AddSyncEventHookup方法多了一个参数true,这是干什么的呢?我们去查看这个看看究竟。

internal void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification) {     AddSyncEventHookup(key, handler, notification, false); }private void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification, bool isPostNotification) {     ThrowIfEventBindingDisallowed();     // add the event to the delegate invocation list     // this keeps non-pipeline ASP.NET hosts working    Events.AddHandler(key, handler);    // For integrated pipeline mode, add events to the IExecutionStep containers only if     // InitSpecial has completed and InitInternal has not completed.    if (IsContainerInitalizationAllowed) {         // lookup the module index and add this notification         PipelineModuleStepContainer container = GetModuleContainer(CurrentModuleCollectionKey);        //WOS 1985878: HttpModule unsubscribing an event handler causes AV in Integrated Mode         if (container != null) {#if DBG            container.DebugModuleName = CurrentModuleCollectionKey;#endif             SyncEventExecutionStep step = new SyncEventExecutionStep(this, (EventHandler)handler);            container.AddEvent(notification, isPostNotification, step);         }     }} 

原来这个方法有2个重新,第2个多了一个isPostNotification的布尔值参数,也就是说通过这个参数节约了很多枚举类型的声明。

我们来仔细看一下上述的代码,在刚开始的时候通过调用Events.AddHandler方法,将事件添加到Events集合里,同时这个key就是我们上面猜想的那些Event+事件名称,通过注释我们也可以知道Events是为non-pipeline来准备的,在结合if语句上面的注释,我们发现在IIS7的集成模式下这些事件是添加到另外一个地方的(通过将事件hanlder包装成SyncEventExecutionStep类型,然后调用container.AddEvent方法将事件添加到另外一个地方),也就是说if上面的Events集合是给IIS6和IIS7经典模式用的,下面的Container里的数据是给IIS7集成模式用的。

注:经典模式使用了Event+事件名称做为key值,但集成模式使用了RequestNotification枚举+ isPostNotification布尔值集合做为key值,这点区别需要注意一下。

那到底IIS7集成模式下的是存放在何处呢?通过GetModuleContainer方法,最终我们可以查到,这些事件是存放在HttpApplication的ModuleContainers属性里,这个属性的类型是PipelineModuleStepContainer[],个数就是HttpModules的个数,也就是说每个HttpModule在HttpApplication上添加的事件都放在各自的PipelineModuleStepContainer容器里。

现在我们重新回头继续来看上个章节的代码:

// Construct the execution steps array if (HttpRuntime.UseIntegratedPipeline) {     _stepManager = new PipelineStepManager(this);} else {    _stepManager = new ApplicationStepManager(this);} _stepManager.BuildSteps(_resumeStepsWaitCallback);

集成模式和经典模式(或IIS6)使用的是不同的StepManager,这个类的BuildSteps方法就是为了创建有序的ExecutionStep,其中包括各种事件的事情以及其它在各时间周期之间穿插的操作,最主要的操作,大家以前就应该知道的,比如哪个周期可以判定使用哪个HttpHandler,以及在哪个周期内执行这个HttpHandler的BeginProcessRequest方法。

 

由于不同的StepManager处理方式不同,我们先看IIS6以及IIS7经典模式的处理代码:

internal override void BuildSteps(WaitCallback stepCallback ) {     ArrayList steps = new ArrayList();    HttpApplication app = _application;    bool urlMappingsEnabled = false;     UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;    urlMappingsEnabled = urlMappings.IsEnabled && ( urlMappings.UrlMappings.Count > 0 );      steps.Add(new ValidateRequestExecutionStep(app));    steps.Add(new ValidatePathExecutionStep(app));     if (urlMappingsEnabled)        steps.Add(new UrlMappingsExecutionStep(app)); // url mappings     app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);    app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);     app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);     app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);    app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);     app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);    app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);    app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);    steps.Add(new MapHandlerExecutionStep(app));     // map handler     app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);    app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);     app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);     app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);    steps.Add(new CallHandlerExecutionStep(app));  // execute handler     app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps);    app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps);    app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps);    steps.Add(new CallFilterExecutionStep(app));  // filtering     app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps);    app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps);     _endRequestStepIndex = steps.Count;     app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps);    steps.Add(new NoopExecutionStep()); // the last is always there     _execSteps = new IExecutionStep[steps.Count];    steps.CopyTo(_execSteps);     // callback for async completion when reposting to threadpool thread    _resumeStepsWaitCallback = stepCallback; } 

看着上面的代码是不是有似曾相识的感觉,很多讲声明周期的文章都会提到20多个的事件(BeginRequest, EndRequest等),我们来看看这个方法的完整功能都是做了什么,归纳总结有5点:

  1. 对请求的Request进行验证,ValidateRequestExecutionStep。
  2. 对请求的路径进行安全检查,禁止非法路径访问(ValidatePathExecutionStep)。 
  3. 如果设置了UrlMappings, 进行RewritePath(UrlMappingsExecutionStep)。
  4. 执行事件处理函数,比如将BeginRequest、AuthenticateRequest转化成可执行ExecutionStep在正式调用时候执行。
  5. 在这18个事件操作处理期间,根据不同的时机加了4个特殊的ExecutionStep。
    1. MapHandlerExecutionStep:查找匹配的HttpHandler
    2. CallHandlerExecutionStep:执行HttpHandler的BeginProcessRequest
    3. CallFilterExecutionStep:调用Response.FilterOutput方法过滤输出
    4. NoopExecutionStep:空操作,留着以后扩展用

需要注意的是所有的ExecuteionStep都保存在ApplicationStepManager实例下的私有字段_execSteps里,而HttpApplication的BeginProcessRequest方法最终会通过该实例的ResumeSteps方法来执行这些操作(就是我们所说的那些事件以及4个特殊的Steps)。

OK,我们继续来看IIS7集成模式下是如何处理的,上代码:

internal override void BuildSteps(WaitCallback stepCallback) {    Debug.Trace("PipelineRuntime", "BuildSteps");     //ArrayList steps = new ArrayList();    HttpApplication app = _application;    // add special steps that don‘t currently     // correspond to a configured handler     IExecutionStep materializeStep = new MaterializeHandlerExecutionStep(app);     // implicit map step     app.AddEventMapping(        HttpApplication.IMPLICIT_HANDLER,        RequestNotification.MapRequestHandler,        false, materializeStep);     // implicit handler routing step     IExecutionStep handlerStep = new CallHandlerExecutionStep(app);     app.AddEventMapping(         HttpApplication.IMPLICIT_HANDLER,        RequestNotification.ExecuteRequestHandler,        false, handlerStep);     // add implicit request filtering step    IExecutionStep filterStep = new CallFilterExecutionStep(app);      // normally, this executes during UpdateRequestCache as a high priority module    app.AddEventMapping(         HttpApplication.IMPLICIT_FILTER_MODULE,        RequestNotification.UpdateRequestCache,        false, filterStep);     // for error conditions, this executes during LogRequest as a high priority module    app.AddEventMapping(         HttpApplication.IMPLICIT_FILTER_MODULE,         RequestNotification.LogRequest,        false, filterStep);     _resumeStepsWaitCallback = stepCallback;}

以上代码有2个地方和IIS6不相同:

  1. IIS7集成模式没有使用MapHandlerExecutionStep来装载ExecutionStep(也就是查找对应的HttpHandler),而是通过MaterializeHandlerExecutionStep类来获得HttpHandler,方式不一样,但最终都是调用HttpApplication.GetFactory方法来获取的,只不过IIS7集成模式有一些特殊操作而已罢了。
  2. IIS7集成模式是通过HttpApplication的AddEventMapping方法来添加事件的,从而将事件再次加入到前面所说的ModuleContainers容器。

另外有个很有技巧的代码:上述4个Steps所加的周期都不是准确的周期,比如CallHandlerExecutionStep应该是加载RequestNotification的枚举值PreExecuteRequestHandler 和ExecuteRequestHandler之间,为什么呢?因为本身CallHandlerExecutionStep只是一个特殊的step而不暴露事件的,所以枚举里也没有,那怎么办?回头看看AddEventMapping方法的第一个参数,它代表的是HttpModule的名字,查看其中的代码得知,在执行所有事件的时候,会遍历所有HttpModuel名称集合然后先执行全部BeginRequest事件,再全部执行AuthenticateRequest事件,以此类推,那我们能不能来伪造一个HttpModule的名称作为参数传递给AddEventMapping方法呢,答案是肯定的,看上面的代码,发现有2个伪造的名称分别是常量字符串HttpApplication.IMPLICIT_FILTER_MODULE(值为AspNetFilterModule)和HttpApplication.IMPLICIT_HANDLER(值为ManagedPipelineHandler),而因为之前其它HttpModule里的各种事件都已经load完了,所以这2个伪造HttpModule的是放在集合的最后面,所以在执行ExecuteRequestHandler类别的事件的时候,最后一个事件肯定就是这个伪造HttpModule的事件,再加上伪造HttpModule里没有别的事件,所以它对应的ExecutionStep的执行效果其实和IIS6里CallHandlerExecutionStep的效果是一样的,就这样,通过一个很奇特的技巧达到同样的目的。

最后,我们来总结一下,在IIS6和IIS7经典模式下,是用 Event+事件名称做key将所有事件的保存在HttpApplication的Events属性对象里,然后在BuildSteps里统一按照顺序组装,中间加载4个特殊的ExecutionStep,最后在统一执行;在IIS7集成模式下,是通过HttpModule名称+RequestNotification枚举值作为key将所有的事件保存在HttpApplication的ModuleContainers属性对象里,然后也在BuildSteps里通过伪造HttpModule名称加载那4个特殊的ExecutionStep,最后按照枚举类型的顺序,遍历所有的HttpModule按顺序来执行这些事件。读者可以自行编写一个自定义的HttpModuel来执行这些事件看看效果如何。

最后关于Pipeline完整的图如下:

 

 

 

最后,需要注意的是:HttpApplication不是HttpRuntime所创建,HttpRuntime只是向HttpApplicationFactory提出请求,要求返回一个HttpApplication对象。 HttpApplicationFactory在接收到请求后,会先检查是否有已经存在并空闲的对象,如果有就取出一个HttpApplication对象返回给HttpRuntime,如果没有的话,则要创建一个HttpApplication对象给HttpRunTime。

 

参考资料:

http://msdn.microsoft.com/en-us/library/bb470252.aspx

http://www.cnblogs.com/zhaoyang/archive/2011/11/16/2251200.html

http://www.brainbell.com/tutorials/ASP/The_ASP.NET_Pipeline.html

http://learn.iis.net/page.aspx/243/aspnet-integration-with-iis/

同步与推荐

本文已同步至目录索引:MVC之前的那点事儿系列

MVC之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

 

原文链接
本文由豆约翰博客备份专家远程一键发布

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下)(转载)