MVC之前的那点事儿系列(5):Http Pipeline详细分析(下)
文章内容
接上面的章节,我们这篇要讲解的是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点:
- 对请求的Request进行验证,ValidateRequestExecutionStep。
- 对请求的路径进行安全检查,禁止非法路径访问(ValidatePathExecutionStep)。
- 如果设置了UrlMappings, 进行RewritePath(UrlMappingsExecutionStep)。
- 执行事件处理函数,比如将BeginRequest、AuthenticateRequest转化成可执行ExecutionStep在正式调用时候执行。
- 在这18个事件操作处理期间,根据不同的时机加了4个特殊的ExecutionStep。
- MapHandlerExecutionStep:查找匹配的HttpHandler
- CallHandlerExecutionStep:执行HttpHandler的BeginProcessRequest
- CallFilterExecutionStep:调用Response.FilterOutput方法过滤输出
- 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不相同:
- IIS7集成模式没有使用MapHandlerExecutionStep来装载ExecutionStep(也就是查找对应的HttpHandler),而是通过MaterializeHandlerExecutionStep类来获得HttpHandler,方式不一样,但最终都是调用HttpApplication.GetFactory方法来获取的,只不过IIS7集成模式有一些特殊操作而已罢了。
- 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之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。