文章內容
接上面的章節,我們這篇要講解的是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之前的那點事兒系列文章,包括了原創,翻譯,轉載等各類型的文章,如果對你有用,請推薦支持一把,給大叔寫作的動力。