代码改变世界

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

2014-06-04 08:50  汤姆大叔  阅读(7433)  评论(4编辑  收藏  举报

文章内容

接上面的章节,我们这篇要讲解的是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之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。