對於ASP.NET開發,排在前五的話題離不開請求生命周期。像什么Cache、身份認證、Role管理、Routing映射,微軟到底在請求過程中干了哪些隱秘的事,現在是時候揭曉了。拋開烏雲見晴天,接下來就一步步揭開請求管道神秘面紗。
上篇回顧
在介紹本篇內容之前,讓我們先回顧下上一篇《ASP.NET運行時詳解 集成模式和經典模式》的主要內容。在上一篇隨筆中,我們提到ASP.NET運行時通過Application的InitInternal方法初始化運行管道。ASP.NET運行時提供了兩種初始化管道模式,集成模式和經典模式。隨后又分別介紹了兩種模式下管道的初始化過程。那么,每個管道具體是做什么事以及管道具體是怎么執行的?接下來,本篇的內容會圍繞着兩個問題進行講解。另外,上篇還遺留了頁面的生命周期介紹。所以,本篇也會對頁面生命周期做介紹。
管道步驟
什么是請求管道?請求管道就是把Application的一系列事件串聯成一條線,這些事件按照排列的先后順序依次執行,事件處理的對象包括HttpModule、HttpHandler、ASP.NET Page。那么,在管道中具體包括哪些事件?下圖概括了ASP.NET請求管道中包括的事件。
現在我們知道了管道包含的執行事件,但每個事件具體執行哪些操作?下面的列表簡要列舉了每個事件的執行的工作:
序號 事件 說明 1 BeginRequest 請求管道的第一個事件,當ASP.NET相應一個請求時就會被觸發。 2 AuthenticateRequest 驗證請求,開始檢查用戶身份,一般是獲取請求的用戶信息。 3 AuthorizeRequest 用戶權限檢查,未通過一般跳轉到EndRequest事件。 4 ResolveRequestCache 當權限驗證通過后,通過緩存模塊提供服務,檢查請求是否存在緩存,存在則直接返回緩存結果。 5 MapRequestHandler ASP.NET 基礎結構使用 MapRequestHandler 事件來確定用於當前請求的請求處理程序。 6 AcquireRequestState 獲取請求狀態。 7 PreExecuteRequestHandler 在ASP.NET執行處理事件handler之前執行。 8 ExecuteRequestHandler 執行具體的Handler。 9 ReleaseRequestState 當 ASP.NET執行完請求的Handler后, State模塊保存當前的狀態數據。 10 UpdateRequestCache 緩存模塊存儲相應,提供給后面的請求緩存。 11 LogRequest 在ASP.NET生成日志之前觸發。 12 EndRequest 結束當前請求。 13 SendResponse 發送請求響應。
列表簡單的描述了管道中包含的事件,每個事件都可以通過HttpModule進行擴展。其實上面這些事件只是一個空架子,而實際干活的還是上一篇隨筆中我們提到的HttpModule,ASP.NET默認實現了很多IHttpModule類,而這些類就是處理篇頭提出的像Cache、身份認證、Role、Rounting等操作。接下來我們就One By One的分析這些Module具體做了什么操作。
之前我們有列舉出了ASP.NET自身提供的IHttpModule,下表包含了ASP.NET自身提供的IHttpModule以及對應的類型:
序號 名稱 類型 1 OutputCacheModule System.Web.Caching.OutputCacheModule 2 Session System.Web.SessionState.SessionStateModule 3 WindowsAuthentication System.Web.Security.WindowsAuthenticationModule 4 FormsAuthentication System.Web.Security.FormsAuthenticationModule 5 DefaultAuthentication System.Web.Security.DefaultAuthenticationModule 6 RoleManager System.Web.Security.RoleManagerModule 7 UrlAuthorization System.Web.Security.UrlAuthorizationModule 8 FileAuthorization System.Web.Security.FileAuthorizationModule 9 AnonymousIdentification System.Web.Security.AnonymousIdentificationModule 10 UrlMappingsModule System.Web.UrlMappingsModule 11 ServiceModel-4.0 System.ServiceModel.Activation.ServiceHttpModule, System.ServiceModel.Activation 12 UrlRoutingModule-4.0 System.Web.Routing.UrlRoutingModule 13 ScriptModule-4.0 System.Web.Handlers.ScriptModule, System.Web.Extensions 列表中的Module會被安插到管道的事件步驟上,但每個Module具體安插到哪一個管道事件上,我們還是不清楚。要了解清楚這些,我們不得不分析這13個Module的源代碼。
13個IHttpModule源代碼分析
為了不影響整篇的閱讀效果,我把13個IHttpModule代碼的詳細介紹放在了附錄。我們不需要全部了解,但是像處理緩存的OutputCacheModule、身份認證的FormsAuthenticationModule、授權Url地址的UrlAuthorizationModule、處理路由映射的UrlRoutingModule等Module是有必要了解的。詳細請查看附錄中的源代碼介紹。
統一管道生廠線
13個Module分析完了,我們也大概知道每個HttpModule應該安插在哪個管道事件上了。上面介紹的IHttpModule,我們通過一張流程圖直觀的展現出來。流程圖如下:
到目前為止,我已經知道了管道中的IHttpModule。但是,只有這些IHttpModule,一次請求的完整流程還是跑不通的。例如,UrlRoutingModule生成了IHttpHandler,但在哪個管道步驟上調用IHttpHandler生成請求頁面我們還是不知道。一個請求的完成流程可以歸納為MHPM。什么事MHPM呢?先看看下面的流程圖:
圖中的MHPM分別表示:IHttpModule、IHttpHandler、Page、IHttpModule。從圖中可以看出,有些IHttpModule在處理IHttpHandler之前執行,而有些IHttpModule在生成頁面Page之后執行。分析了所有的IHttpModule,但我們還是沒看到執行IHttpHandler的ExecuteRequestHandler管道上有任何附加操作。回想上一篇隨筆,我們還記得集成模式的管道類PipelineStepManager有一個BuildSteps方法,部分代碼如下:
internal override void BuildSteps(WaitCallback stepCallback) { HttpApplication.IExecutionStep step2 = new HttpApplication.CallHandlerExecutionStep(app); app.AddEventMapping("ManagedPipelineHandler", RequestNotification.ExecuteRequestHandler, false, step2); }
代碼中實例化了一個執行步驟step2,然后把step2映射到管道的ExecuteRequestHandler步驟。CallHandlerExecutionStep實現了管道步驟接口IExecutionStep。通過實現接口的Execute方法執行IHttpHandler的ProcessRequest方法。Execute代碼如下:
void HttpApplication.IExecutionStep.Execute() { HttpContext context = this._application.Context; IHttpHandler handler = context.Handler; if (handler == null) { this._sync = true; } else if (handler is IHttpAsyncHandler) { IAsyncResult result; bool flag; bool flag2; IHttpAsyncHandler handler2 = (IHttpAsyncHandler)handler; this._sync = false; this._handler = handler2; Func<HttpContext, AsyncCallback, object, IAsyncResult> func = AppVerifier.WrapBeginMethod<HttpContext>(this._application, new Func<HttpContext, AsyncCallback, object, IAsyncResult>(handler2.BeginProcessRequest)); result = func(context, this._completionCallback, null); this._asyncStepCompletionInfo.RegisterBeginUnwound(result, out flag, out flag2); if (flag) { handler2.EndProcessRequest(result); } } else { this._sync = true; handler.ProcessRequest(context); } }代碼首先對handler做判斷,判斷handler是否是異步類IHttpAsyncHandler。如果是,則執行Handler的異步方法:BeginProcessRequest;如果不是,則直接調用handler的同步方法ProcessRequest。
CallHandlerExecutionStep步驟執行完后,ASP.NET就能得到具體的ASP.NET Page頁面。在ProcessRequest執行過程中,涉及到頁面的生成周期。對於頁面的生命周期,我們必須區分WEBFORM頁面和MVC頁面。兩種不同的頁面,生命周期也完全不同。WEBFORM是基於事件驅動,但MVC頁面已經不再基於事件驅動。ExecuteRequestHandler管道事件上現在也附件的有操作了。目前為止,ASP.NET執行過程的整個管道步驟我們也差不多涉及的有個90%了。了解清楚請求過程的生命周期是非常有必要的。了解清楚了請求過程原理,我們可以設計出更加靈活的ASP.NET系統,並且能基於ASP.NET做更多的自定義擴展。
總結
本篇內容首先分析了ASP.NET執行管道包含哪些事件。但最初這些管道只是一個空架子,而在管道事件上添加具體任務是有IHttpModule完成。微軟自己為ASP.NET執行管道實現了13個IHttpModule接口,並且這13個Module分布在不同的管道是事件上。本篇我們也具體介紹了這13個Module具體分布在哪些管道是事件上,以及每個Module在管道事件上具體做了什么操作。
本篇也簡單的介紹了ExecuteRequestHandler管道事件上的IHttpHandler任務怎樣執行。但沒有具體介紹IHttpHandler是怎樣生成我們需要的ASP.NET Page頁面,也既是頁面的生命周期。所以,下一篇隨筆的預定內容既是ASP.NET高頻話題:ASP.NET頁面生命周期。
如果本篇內容對大家有幫助,請點擊頁面右下角的關注。如果覺得不好,也歡迎拍磚。你們的評價就是博主的動力!下篇內容,敬請期待!
附錄
1.OutputCacheModule
所在管道步驟:ResolveRequestCache、UpdateRequestCache。查看OutputCacheModule實現的Init方法,代碼如下:
void IHttpModule.Init(HttpApplication app) { if (RuntimeConfig.GetAppConfig().OutputCache.EnableOutputCache) { app.ResolveRequestCache += new EventHandler(this.OnEnter); app.UpdateRequestCache += new EventHandler(this.OnLeave); } }
通過代碼我們能看出它在ResolveRequestCache和UpdateRequestCache這兩個管道事件上執行了某些操作。OnEnter事件查看緩存記錄是否有緩存,有緩存則直接返回緩存,而不執行之后的管道流程了。代碼如下:
internal void OnEnter(object source, EventArgs eventArgs) { if (OutputCache.InUse) { switch (request.HttpVerb) { case HttpVerb.GET: case HttpVerb.HEAD: case HttpVerb.POST: { string str; this._key = str = this.CreateOutputCachedItemKey(context, null); object obj2 = OutputCache.Get(str); if (obj2 != null) { response.Cache.ResetFromHttpCachePolicySettings(settings, context.UtcTimestamp); string originalCacheUrl = response2._kernelCacheUrl; if (originalCacheUrl != null) { response.SetupKernelCaching(originalCacheUrl); } PerfCounters.IncrementCounter(AppPerfCounter.OUTPUT_CACHE_RATIO_BASE); PerfCounters.IncrementCounter(AppPerfCounter.OUTPUT_CACHE_HITS); this._key = null; this._recordedCacheMiss = false; application.CompleteRequest(); return; } return; } } } }其實OnEnter里邊的代碼比我粘貼出來的多很多,但主流程是一致的。都是先通過請求的上下文信息(例如RequestPath、Method參數等)獲取緩存主鍵,然后通過緩存主鍵到緩存隊列里邊去查看是否有主鍵對應的緩存。如果有緩存,則直接把緩存輸出到Response.Output中,然后整個流程請求流程結束;如果沒有緩存,則按管道流程執行下一步。
既然在OnEnter里邊能取出來緩存,那么肯定有寫緩存的地方。寫緩存正式通過OutputCacheModule的OnLeave方法寫入,OnLeave方法代碼如下:internal void OnLeave(object source, EventArgs eventArgs) { bool flag = false; if (response.HasCachePolicy) { cache = response.Cache; if (((cache.IsModified() && (response.StatusCode == 200)) && ((request.HttpVerb == HttpVerb.GET) || (request.HttpVerb == HttpVerb.POST))) && response.IsBuffered()) { if (((((cache.GetCacheability() == HttpCacheability.Public) || (cache.GetCacheability() == HttpCacheability.ServerAndPrivate)) || ((cache.GetCacheability() == HttpCacheability.Server) || flag3)) && ((!cache.GetNoServerCaching() && !response.ContainsNonShareableCookies()) && (cache.HasExpirationPolicy() || cache.HasValidationPolicy()))) && ((!cache.VaryByHeaders.GetVaryByUnspecifiedParameters() && (cache.VaryByParams.AcceptsParams() || ((request.HttpVerb != HttpVerb.POST) && !request.HasQueryString))) && (!cache.VaryByContentEncodings.IsModified() || cache.VaryByContentEncodings.IsCacheableEncoding(context.Response.GetHttpHeaderContentEncoding())))) { flag = true; } } } if (flag) { CachedVary vary; string str; string[] varyByParams; this.RecordCacheMiss(); HttpCachePolicySettings currentSettings = cache.GetCurrentSettings(response); string[] varyByContentEncodings = currentSettings.VaryByContentEncodings; string[] varyByHeaders = currentSettings.VaryByHeaders; if (this._key == null) { this._key = this.CreateOutputCachedItemKey(context, null); } DateTime noAbsoluteExpiration = Cache.NoAbsoluteExpiration; TimeSpan noSlidingExpiration = Cache.NoSlidingExpiration; if (currentSettings.SlidingExpiration) { noSlidingExpiration = currentSettings.SlidingDelta; } else if (currentSettings.IsMaxAgeSet) { DateTime time2 = (currentSettings.UtcTimestampCreated != DateTime.MinValue) ? currentSettings.UtcTimestampCreated : context.UtcTimestamp; noAbsoluteExpiration = time2 + currentSettings.MaxAge; } if (noAbsoluteExpiration > DateTime.UtcNow) { HttpRawResponse snapshot = response.GetSnapshot(); string kernelCacheUrl = response.SetupKernelCaching(null); Guid cachedVaryId = (vary != null) ? vary.CachedVaryId : Guid.Empty; CachedRawResponse rawResponse = new CachedRawResponse(snapshot, currentSettings, kernelCacheUrl, cachedVaryId); CacheDependency dependencies = response.CreateCacheDependencyForResponse(); OutputCache.InsertResponse(this._key, vary, str, rawResponse, dependencies, noAbsoluteExpiration, noSlidingExpiration); } } }代碼首先檢查請求頭和響應頭,看看是否符合寫緩存的條件,例如檢查緩存是否修改、返回狀態是否為200等。接下來創建緩存主鍵、設置緩存周期。最后一步就是通過OutputCache.InsertResponse方法把結果緩存到OutputCache中。
2. SessionStateModule
所在管道步驟:AcquireRequestState、ReleaseRequestState、EndRequest。SessionStateModule的Init方法調用了InitModuleFromConfig方法,從配置文件中讀取配置,初始化狀態存儲。在Web.config配置中我們經常看到<sessionState mode="InProc" cookieless="UseCookies" />配置,mode包括InProc(進程內)、SQLServer(數據庫)、StateServer(進程外)、Custom(自定義)、Off(關閉Session)等。我們先看下InitModuleFromConfig方法的代碼:
private void InitModuleFromConfig(HttpApplication app, SessionStateSection config) { if (config.Mode != SessionStateMode.Off) { app.AddOnAcquireRequestStateAsync(new BeginEventHandler(this.BeginAcquireState), new EndEventHandler(this.EndAcquireState)); app.ReleaseRequestState += new EventHandler(this.OnReleaseState); app.EndRequest += new EventHandler(this.OnEndRequest); this._partitionResolver = this.InitPartitionResolver(config); switch (config.Mode) { case SessionStateMode.InProc: if (HttpRuntime.UseIntegratedPipeline) { s_canSkipEndRequestCall = true; } this._store = new InProcSessionStateStore(); this._store.Initialize(null, null); break; case SessionStateMode.StateServer: if (HttpRuntime.UseIntegratedPipeline) { s_canSkipEndRequestCall = true; } this._store = new OutOfProcSessionStateStore(); ((OutOfProcSessionStateStore)this._store).Initialize(null, null, this._partitionResolver); break; case SessionStateMode.SQLServer: this._store = new SqlSessionStateStore(); ((SqlSessionStateStore)this._store).Initialize(null, null, this._partitionResolver); break; case SessionStateMode.Custom: this._store = this.InitCustomStore(config); break; } this._idManager = this.InitSessionIDManager(config); if (((config.Mode == SessionStateMode.InProc) || (config.Mode == SessionStateMode.StateServer)) && this._usingAspnetSessionIdManager) { this._ignoreImpersonation = true; } } }前面的幾行代碼加載Session事件到執行管道,接下來的switch代碼根據Mode的枚舉值初始化不同的Session存儲介質。 后面還有一行代碼調用了InitSessionIDManager方法,生成一個Session的ID管理器。
當管道執行到AcquireRequestState事件時,SessionStateModule中的BeginAcquireState事件被觸發,精簡后的代碼如下:
private IAsyncResult BeginAcquireState(object source, EventArgs e, AsyncCallback cb, object extraData) { this.ResetPerRequestFields(); this._rqContext = ((HttpApplication)source).Context; this._rqAr = new HttpAsyncResult(cb, extraData); this.ChangeImpersonation(this._rqContext, false); this._store.InitializeRequest(this._rqContext); if (this._idManager.InitializeRequest(this._rqContext, false, out this._rqSupportSessionIdReissue)) { //不使用Cookie直接結束 this._rqAr.Complete(true, null, null); return this._rqAr; } this._rqId = this._idManager.GetSessionID(this._rqContext); this._rqExecutionTimeout = this._rqContext.Timeout; this._rqReadonly = this._rqContext.ReadOnlySessionState; if (this._rqId != null) { sessionStateItem = this.GetSessionStateItem(); } else if (!flag3) { bool flag4 = this.CreateSessionId(); this._rqIdNew = true; if (flag4) { if (s_configRegenerateExpiredSessionId) { this.CreateUninitializedSessionState(); } this._rqAr.Complete(true, null, null); return this._rqAr; } } if (sessionStateItem) { this.CompleteAcquireState(); this._rqAr.Complete(true, null, null); } result = this._rqAr; return result; }代碼首先調用存儲介質_store的InitializeRequest方法,初始化本次請求。然后調用ID管理器_idManager的InitializeRequest初始化請求,InitializeRequest方法會返回一個布爾值,為true表示不使用cookie,直接返回;為false表示使用cookie,繼續執行BeginAcquireState接下來的流程。初始化完成后調用_idManager.GetSessionID方法獲取SessionID。如果沒有獲取到SessionID,則調用CreateSessionId生成SessionID。
當管道執行到ReleaseRequestState步驟時,SessionStateModule中的OnReleaseState事件被觸發。我們知道在BeginAcquireState事件中已經生成了SessionID。所以,在ReleaseRequestState中我們能夠獲取到SessionID,然后根據session狀態調用_store.RemoveItem方法移除緩存項或者調用_store SetAndReleaseItemExclusive方法插入、更新或者移除緩存項。
當管道執行到EndRequest步驟時,SessionStateModule中的OnEndRequest事件被觸發。這里邊主要的內容就是初始化請求參數以及重置超時事件,准備接收下一次請求。3. WindowsAuthenticationModule
所在管道步驟:AuthenticateRequest。WindowsAuthticationModule的Init方法在管道的AuthenticateRequest步驟注冊OnEnter事件,OnEnter執行的內容比較簡單,從上下文中取出用戶身份,然后把用戶身份設置到上下文的安全實體WindowsPrincipal中。
4. FormsAuthenticationModule
所在管道步驟:AuthenticateRequest、EndRequest。FormsAuthenticationModule的Init方法代碼如下:
public void Init(HttpApplication app) { if (!_fAuthChecked) { _fAuthRequired = AuthenticationConfig.Mode == AuthenticationMode.Forms; _fAuthChecked = true; } if (_fAuthRequired) { FormsAuthentication.Initialize(); app.AuthenticateRequest += new EventHandler(this.OnEnter); app.EndRequest += new EventHandler(this.OnLeave); } }代碼調用了FormsAuthentication.Initialize()方法對表單驗證做初始化操作。Initialize方法代碼如下:
public static void Initialize() { AuthenticationSection authentication = RuntimeConfig.GetAppConfig().Authentication; authentication.ValidateAuthenticationMode(); _FormsName = authentication.Forms.Name; _RequireSSL = authentication.Forms.RequireSSL; _SlidingExpiration = authentication.Forms.SlidingExpiration; if (_FormsName == null) { _FormsName = ".ASPXAUTH"; } _Protection = authentication.Forms.Protection; _Timeout = (int)authentication.Forms.Timeout.TotalMinutes; _FormsCookiePath = authentication.Forms.Path; _LoginUrl = authentication.Forms.LoginUrl; if (_LoginUrl == null) { _LoginUrl = "login.aspx"; } _DefaultUrl = authentication.Forms.DefaultUrl; if (_DefaultUrl == null) { _DefaultUrl = "default.aspx"; } _CookieMode = authentication.Forms.Cookieless; _CookieDomain = authentication.Forms.Domain; _EnableCrossAppRedirects = authentication.Forms.EnableCrossAppRedirects; _TicketCompatibilityMode = authentication.Forms.TicketCompatibilityMode; _Initialized = true; }通過代碼可以看出,Initialize方法從配置文件中讀取表單配置信息並初始化到FormsAuthentication類的靜態字段中。Cookieless指定Cookie類型, defaultUrl表示驗證后重定向的默認地址,loginUrl表示找不到驗證cookie重定向的登錄地址,protection指定cookie的加密類型。詳細說明請可以查看MSDN:https://msdn.microsoft.com/zh-cn/library/1d3t3c61.aspx。 在管道AuthenticateRequest步驟上,我們注冊了OnEnter方法,代碼如下:
private void OnEnter(object source, EventArgs eventArgs) { HttpContext context = application.Context; this.OnAuthenticate(new FormsAuthenticationEventArgs(context)); CookielessHelperClass cookielessHelper = context.CookielessHelper; if (AuthenticationConfig.AccessingLoginPage(context, FormsAuthentication.LoginUrl)) { context.SetSkipAuthorizationNoDemand(true, false); cookielessHelper.RedirectWithDetectionIfRequired(null, FormsAuthentication.CookieMode); } if (!context.SkipAuthorization) { context.SetSkipAuthorizationNoDemand(AssemblyResourceLoader.IsValidWebResourceRequest(context), false); } }分析代碼,首先調用了OnAuthenticate方法,OnAuthenticate通過Cookie配置信息對每次的請求作Cookie更新,例如如果設置Cookie為可調過期(Slid),那么每次請求都會對cookie的過期時間更新。然后調用了AuthenticationConfig.AccessingLoginPage方法,判斷是否正在請求配置的LoginUrl,如果是則直接跳過授權步驟。如果沒有跳過授權步驟,檢查當前請求是否為Web資源請求,如果是則直接跳過授權步驟。在EndRequest管道步驟上,我們注冊了OnLeave方法,代碼如下:
private void OnLeave(object source, EventArgs eventArgs) { HttpApplication application = (HttpApplication)source; HttpContext context = application.Context; //context.Response.StatusCode == 401 && buzh if ((context.Response.StatusCode == 0x191) && !context.Response.SuppressFormsAuthenticationRedirect) { //當前請求的原始地址 string rawUrl = context.Request.RawUrl; if ((rawUrl.IndexOf("?" + FormsAuthentication.ReturnUrlVar + "=", StringComparison.Ordinal) == -1) && (rawUrl.IndexOf("&" + FormsAuthentication.ReturnUrlVar + "=", StringComparison.Ordinal) == -1)) { strUrl = AuthenticationConfig.GetCompleteLoginUrl(context, FormsAuthentication.LoginUrl); CookielessHelperClass cookielessHelper = context.CookielessHelper; if (strUrl.IndexOf('?') >= 0) { strUrl = FormsAuthentication.RemoveQueryStringVariableFromUrl(strUrl, FormsAuthentication.ReturnUrlVar); str3 = strUrl + "&" + FormsAuthentication.ReturnUrlVar + "=" + HttpUtility.UrlEncode(rawUrl, context.Request.ContentEncoding); } else { str3 = strUrl + "?" + FormsAuthentication.ReturnUrlVar + "=" + HttpUtility.UrlEncode(rawUrl, context.Request.ContentEncoding); } int index = rawUrl.IndexOf('?'); if ((index >= 0) && (index < (rawUrl.Length - 1))) { str3 = str3 + "&" + rawUrl.Substring(index + 1); } cookielessHelper.SetCookieValue('F', null); cookielessHelper.RedirectWithDetectionIfRequired(str3, FormsAuthentication.CookieMode); context.Response.Redirect(str3, false); } } }先列舉一個場景,例如我們在taobao首頁查看到某個商品,點擊“購買”,但我們還沒有登錄。這個時候taobao會跳轉到登錄界面,登錄成功后直接跳轉到你的購買界面。OnLeave所做的正是這樣的工作,首先校驗返回狀態status是否為0x191(401,權限驗證失敗),如果是則獲取登錄界面地址,並且在后面加上ReturnUrl=請求地址。例如我請求地址是http://buy.heavi.com,但請求狀態返回了401,這則時候OnLeave拼湊登錄地址:http://login.heavi.com? ReturnUrl=http://buy.heavi.com。最后直接通過context.Response.Redirect(str3, false)重定向到登錄界面。當登錄成功后,在重定向到http://buy.heavi.com界面。
5. DefaultAuthenticationModule
所在管道步驟:AuthenticateRequest。DefaultAuthenticationModule的Init把OnEnter方法注冊到AuthenticateRequest管道步驟上。OnEnter代碼如下:
private void OnEnter(object source, EventArgs eventArgs) { if (context.Response.StatusCode > 200) { if (context.Response.StatusCode == 0x191) { this.WriteErrorMessage(context); } application.CompleteRequest(); } else { if (context.User == null) { this.OnAuthenticate(new DefaultAuthenticationEventArgs(context)); if (context.Response.StatusCode > 200) { if (context.Response.StatusCode == 0x191) { this.WriteErrorMessage(context); } application.CompleteRequest(); return; } } if (context.User == null) { context.SetPrincipalNoDemand(new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]), false); } Thread.CurrentPrincipal = context.User; } }代碼也比較簡單,判斷Response中的Status狀態是等於401,是則寫日志,直接結束本次請求。不是則設置當前線程的CurrentPrincipal為當前請求的用戶。
6. RoleManagerModule
所在管道步驟:AuthenticateRequest、EndRequest。RoleManagerModule的Init方法把OnEnter方法注冊到AuthenticateRequest管道步驟上,把OnLeave方法注冊到EndRequest。OnEnter方法代碼如下:
private void OnEnter(object source, EventArgs eventArgs) { if (Roles.CacheRolesInCookie) { if (context.User.Identity.IsAuthenticated && (!Roles.CookieRequireSSL || context.Request.IsSecureConnection)) { HttpCookie cookie = context.Request.Cookies[Roles.CookieName]; if (cookie != null) { string encryptedTicket = cookie.Value; if (!string.IsNullOrEmpty(Roles.CookiePath) && (Roles.CookiePath != "/")) { cookie.Path = Roles.CookiePath; } cookie.Domain = Roles.Domain; context.SetPrincipalNoDemand(this.CreateRolePrincipalWithAssert(context.User.Identity, encryptedTicket)); } } else { if (context.Request.Cookies[Roles.CookieName] != null) { Roles.DeleteCookie(); } if (HttpRuntime.UseIntegratedPipeline) { context.DisableNotifications(RequestNotification.EndRequest, 0); } } } if (!(context.User is RolePrincipal)) { context.SetPrincipalNoDemand(this.CreateRolePrincipalWithAssert(context.User.Identity, null)); } HttpApplication.SetCurrentPrincipalWithAssert(context.User); }如果設置了CacheRolesInCookie,並且身份已經通過認證了。接下來就從請求中獲取Role的Cookie,並使用認證的身份創建角色安全體保存到上下文中;如果認證沒通過,並且Cookie中有角色的Cookie,則刪除角色Cookie。OnLeave代碼如下:
private void OnLeave(object source, EventArgs eventArgs) { if (((Roles.Enabled && Roles.CacheRolesInCookie) && !context.Response.HeadersWritten) && (((context.User != null) && (context.User is RolePrincipal)) && context.User.Identity.IsAuthenticated)) { if (Roles.CookieRequireSSL && !context.Request.IsSecureConnection) { if (context.Request.Cookies[Roles.CookieName] != null) Roles.DeleteCookie(); } else { RolePrincipal user = (RolePrincipal)context.User; if (user.CachedListChanged && context.Request.Browser.Cookies) { string str = user.ToEncryptedTicket(); if (string.IsNullOrEmpty(str) || (str.Length > 0x1000)) Roles.DeleteCookie(); else { HttpCookie cookie = new HttpCookie(Roles.CookieName, str) { HttpOnly = true, Path = Roles.CookiePath, Domain = Roles.Domain }; if (Roles.CreatePersistentCookie) { cookie.Expires = user.ExpireDate; } cookie.Secure = Roles.CookieRequireSSL; context.Response.Cookies.Add(cookie); } } } } }首先判斷角色是否可用、是否把角色緩存存儲在Cookie、上下文身份是否是角色安全體、是否通過認證,只有滿足這些條件才執行下面的流程。滿足條件后,如果Cookie需要SSL認證並且不是安全連接,則刪除Cookie中的角色Cookie;否則,重新生成新的Cookie並返回到Response中。
7. UrlAuthorizationModule
所在管道步驟:AuthorizeRequest。UrlAuthorizationModule的Init把OnEnter方法注冊到AuthorizeRequest管道步驟上。OnEnter方法代碼如下:
private void OnEnter(object source, EventArgs eventArgs) { AuthorizationSection authorization = RuntimeConfig.GetConfig(context).Authorization; if (!authorization.EveryoneAllowed && !authorization.IsUserAllowed(context.User, context.Request.RequestType)) { ReportUrlAuthorizationFailure(context, this); } else { if ((context.User == null) || !context.User.Identity.IsAuthenticated) { PerfCounters.IncrementCounter(AppPerfCounter.ANONYMOUS_REQUESTS); } WebBaseEvent.RaiseSystemEvent(this, 0xfa3); } }首先從配置中獲取授權節點,如果當前用戶被限制,則調用ReportUrlAuthorizationFailure方法記錄Url授權報告並終止本次請求;如果授權成功,執行WebSuccessAuditEvent系統事件。
8. FileAuthorizationModule
所在管道步驟:AuthorizeRequest。FileAuthorizationModule的Init把OnEnter方法注冊到AuthorizeRequest管道步驟上。OnEnter代碼如下:
private void OnEnter(object source, EventArgs eventArgs) { if (!IsUserAllowedToFile(context, null)) { context.Response.SetStatusCode(0x191, 3); this.WriteErrorMessage(context); application.CompleteRequest(); } }代碼中,調用IsUserAllowedToFile方法判斷當前用戶是否允許訪問請求的文件。如果不允許訪問,則設置返回狀態為401(認證失敗)並記錄錯誤信息,結束本次請求。需要說明的是,IsUserAllowedToFile只驗證Windows用戶。如果是其他用戶,則不需要File驗證。
9. AnonymousIdentificationModule
所在管道步驟:AuthorizeRequest。AnonymousIdentificationModule的Init把OnEnter方法注冊到AuthorizeRequest管道步驟上。OnEnter代碼如下:
private void OnEnter(object source, EventArgs eventArgs) { if (!s_Initialized) //從配置文件中讀取anonymousIdentification節點配置 Initialize(); if (s_Enabled) { isAuthenticated = context.Request.IsAuthenticated; if (isAuthenticated) flag2 = CookielessHelperClass.UseCookieless(context, false, s_CookieMode); //false表示使用cookie else flag2 = CookielessHelperClass.UseCookieless(context, true, s_CookieMode); //true表示不適用cookie //如果需要SSL,並且請求不是安全連接,並且使用cookie if ((s_RequireSSL && !context.Request.IsSecureConnection) && !flag2) { if (context.Request.Cookies[s_CookieName] != null) { //重新設置Cookie,並且設置過期時間為已過期,0x7cf表示1999年。 cookie = new HttpCookie(s_CookieName, string.Empty) { HttpOnly = true, Path = s_CookiePath, Secure = s_RequireSSL }; cookie.Expires = new DateTime(0x7cf, 10, 12); context.Response.Cookies.Add(cookie); } } //不需要SSL認證 else { if (!flag2) { cookie = context.Request.Cookies[s_CookieName]; if (cookie != null) { cookieValue = cookie.Value; cookie.Path = s_CookiePath; cookie.Domain = s_Domain; } } else { cookieValue = context.CookielessHelper.GetCookieValue('A'); } decodedValue = GetDecodedValue(cookieValue); if ((decodedValue != null) && (decodedValue.AnonymousId != null)) { context.Request.AnonymousID = decodedValue.AnonymousId; } if (!isAuthenticated) { //設置AnonymousID if (context.Request.AnonymousID == null) { if (this._CreateNewIdEventHandler != null) { AnonymousIdentificationEventArgs e = new AnonymousIdentificationEventArgs(context); this._CreateNewIdEventHandler(this, e); context.Request.AnonymousID = e.AnonymousID; } flag = true; } DateTime utcNow = DateTime.UtcNow; //如果cookie設置為滑動調整,並且cookie過期時間小於cookie過期周期的一半,則需要更新cookie if (!flag && s_SlidingExpiration) { if ((decodedValue == null) || (decodedValue.ExpireDate < utcNow)) { flag = true; } else { TimeSpan span = (TimeSpan)(decodedValue.ExpireDate - utcNow); if (span.TotalSeconds < ((s_CookieTimeout * 60) / 2)) { flag = true; } } } //生成新的cookie if (flag) { DateTime dt = utcNow.AddMinutes((double)s_CookieTimeout); cookieValue = GetEncodedValue(new AnonymousIdData(context.Request.AnonymousID, dt)); if (!flag2) { cookie = new HttpCookie(s_CookieName, cookieValue) { HttpOnly = true, Expires = dt, Path = s_CookiePath, Secure = s_RequireSSL }; if (s_Domain != null) { cookie.Domain = s_Domain; } context.Response.Cookies.Add(cookie); } else { context.CookielessHelper.SetCookieValue('A', cookieValue); context.Response.Redirect(context.Request.RawUrl); } } } } }首先調用Initialize方法從配置文件中讀取anonymousIdentification節點配置信息,例如我們在Web.Config中配置:
<anonymousIdentification enabled="true" cookieName="anonyIdentity" cookiePath="/Cookie/" cookieTimeout="60" cookieRequireSSL="true" cookieSlidingExpiration="true" />Initialize方法把這些配置讀取到AnonymousIdentificationModule實體中。如果匿名身份需要SSL認證並且當前連接不是安全連接,則直接把Cookie設置為已過期並返回到Response中。如果不需要SSL認證,則根據配置信息以及過期周期更新匿名Cookie的AnonymousID以及過期時間,最后把更新的Cookie返回到Response.Cookie中。
10. UrlMappingsModule
所在管道步驟:BeginRequest。UrlMappingsModule的Init做了兩件事,一是從配置文件中讀取urlMappings 節點配置,下面就是Web.cofnig中配置實例:
<urlMappings enabled="true"> <add url="/UserInfo/Index" mappedUrl="/Home/Index" /> </urlMappings>Init的第二件事就是把OnEnter方法注冊到BeginRequest管道步驟,OnEnter方法直接調用UrlMappingRewritePath方法,所以,我們可以直接分析UrlMappingRewritePath方法代碼:
static void UrlMappingRewritePath(HttpContext context) { HttpRequest request = context.Request; UrlMappingsSection urlMappings = RuntimeConfig.GetAppConfig().UrlMappings; string path = request.Path; string str2 = null; string queryStringText = request.QueryStringText; if (!string.IsNullOrEmpty(queryStringText)) { str2 = urlMappings.HttpResolveMapping(path + "?" + queryStringText); } if (str2 == null) { str2 = urlMappings.HttpResolveMapping(path); } if (!string.IsNullOrEmpty(str2)) { context.RewritePath(str2, false); } }代碼比較簡單,首先從配置文件中獲取urlMappings節點信息,然后調用HttpResolveMapping方法,匹配請求的全路徑url(包括路徑和參數)是否有對應的mappedUrl。如果沒有,再匹配請求的路徑path是否有對應的mappedUrl。匹配成功,調用context.RewirtePath方法設置請求的路徑為mappedUrl。
11. ServiceHttpModule
ServiceHttpModule沒有執行任何操作。用戶向后擴展
12. UrlRoutingModule
UrlRoutingModule在所有管道中起到承上啟下的作用,Http請求的IHttpHandler就在是這里生成的。所在管道步驟:ResolveRequestCache。Init方法把UrlRoutingModule中的OnApplicationPostResolveRequestCache方法注冊到ResolveRequestCache管道步驟。
OnApplicationPostResolveRequestCache方法直接調用了PostResolveRequestCache方法,PostResolveRequestCache代碼如下:public virtual void PostResolveRequestCache(HttpContextBase context) { //根據上下文從路由集合中獲取對應路由數據 RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData != null) { //獲取路由處理器 IRouteHandler routeHandler = routeData.RouteHandler; if (!(routeHandler is StopRoutingHandler)) { RequestContext requestContext = new RequestContext(context, routeData); context.Request.RequestContext = requestContext; //獲取IHttpHandler IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); //重定向上下文中的httpHandler context.RemapHandler(httpHandler); } } }上面的代碼已經是一目了然,清清楚楚的了。首先從路由集合中獲取路由數據routeData,然后從routeData獲取RouteHandler,接下來調用routeHandler的GetHttpHandler方法獲取IHttpHandler實例。最后,調用上下文context的RemapHandler方法重定向httpHandler。下面是整個執行的流程圖:
13. ScriptModule
ScriptModule沒有執行任何操作。
如果本篇內容對大家有幫助,請點擊頁面右下角的關注。如果覺得不好,也歡迎拍磚。你們的評價就是博主的動力!下篇內容,敬請期待!




