文章內容
話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime繼承了IISPAIRuntime介面,該介面可以和COM進行互動,並且暴露了ProcessRequest介面方法)。至於為什麼要呼叫這個方法,大叔也不太清楚,找不到微軟相關的資料哦。但大叔確定該方法就是我們進入HttpRuntime的正式大門,接著看吧。
public int ProcessRequest(IntPtr ecb, int iWRType) { IntPtr pHttpCompletion = IntPtr.Zero; if (iWRType == WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) { pHttpCompletion = ecb; ecb = UnsafeNativeMethods.GetEcb(pHttpCompletion); } ISAPIWorkerRequest wr = null; try { bool useOOP = (iWRType == WORKER_REQUEST_TYPE_OOP); wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP); wr.Initialize(); // check if app path matches (need to restart app domain?) String wrPath = wr.GetAppPathTranslated(); String adPath = HttpRuntime.AppDomainAppPathInternal; if (adPath == null || StringUtil.EqualsIgnoreCase(wrPath, adPath)) { HttpRuntime.ProcessRequestNoDemand(wr); return 0; } else { // need to restart app domain HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString(SR.Hosting_Phys_Path_Changed, adPath, wrPath)); return 1; } } catch(Exception e) { try { WebBaseEvent.RaiseRuntimeError(e, this); } catch {} // Have we called HSE_REQ_DONE_WITH_SESSION? If so, don't re-throw. if (wr != null && wr.Ecb == IntPtr.Zero) { if (pHttpCompletion != IntPtr.Zero) { UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion); } // if this is a thread abort exception, cancel the abort if (e is ThreadAbortException) { Thread.ResetAbort(); } // IMPORTANT: if this thread is being aborted because of an AppDomain.Unload, // the CLR will still throw an AppDomainUnloadedException. The native caller // must special case COR_E_APPDOMAINUNLOADED(0x80131014) and not // call HSE_REQ_DONE_WITH_SESSION more than once. return 0; } // re-throw if we have not called HSE_REQ_DONE_WITH_SESSION throw; } }
第一個注意到的就是該方法的IntPtr型別的引數ecb,ecb是啥?ecb是一個非託管的指標,全稱是Execution Control Block,在整個Http Request Processing過程中起著非常重要的作用,我們現在來簡單介紹一個ECB。
非託管環境ISAPI對ISAPIRuntime的呼叫,需要傳遞一些必須的資料,比如ISAPIRuntime要獲取Server Variable的資料,獲取通過Post Mehod傳回Server的資料;以及最終將Response的內容返回給非託管環境ISAPI,然後呈現給Client使用者。一般地ISAPIRuntime不能直接呼叫ISAPI,所以這裡就通過一個物件指標實現對其的呼叫,這個物件就是ECB,ECB實現了對非託管環境ISAPI的訪問。
還有一點特別需要強調的是,ISAPI對ISAPIRutime的呼叫是非同步的,也就是說ISAPI呼叫ISAPIRutime之後立即返回。這主要是出於Performance和Responsibility考慮的,因為ASP.NET Application天生就是一個多執行緒的應用,為了具有更好的響應能力,非同步操作是最有效的解決方式。但是這裡就會有一個問題,我們知道我們對ASP.NET 資源的呼叫本質上是一個Request/Response的Message Exchange Pattern,非同步呼叫往往意味著ISAPI將Request傳遞給ISAPIRuntime,將不能得到ISAPIRuntime最終生成的Response,這顯然是不能接受的。而ECB解決了這個問題,ISAPI在呼叫ISAPIRutime的ProcessRequest方法時會將自己對應的ECB的指標傳給它,ISAPIRutime不但可以將最終生成的Response返回給ISAPI,還能通過ECB呼叫ISAPI獲得一些所需的資料。
上述程式碼裡第2個加粗的程式碼是執行ISAPIWorkerRequest的靜態方法CreateWorkerRequest從而建立ISAPIWorkerRequest物件例項,引數分別為ecb和代表WorkerRequest型別的int引數iWRType,讓我們來看看這個方法的程式碼:
internal static ISAPIWorkerRequest CreateWorkerRequest(IntPtr ecb, bool useOOP) { ISAPIWorkerRequest wr = null; if (useOOP) { EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero); if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, false); wr = new ISAPIWorkerRequestOutOfProc(ecb); } else { int version = UnsafeNativeMethods.EcbGetVersion(ecb) >> 16; if (version >= 7) { EtwTrace.TraceEnableCheck(EtwTraceConfigType.IIS7_ISAPI, ecb); } else { EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero); } if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, true); if (version >= 7) { wr = new ISAPIWorkerRequestInProcForIIS7(ecb); } else if (version == 6) { wr = new ISAPIWorkerRequestInProcForIIS6(ecb); } else { wr = new ISAPIWorkerRequestInProc(ecb); } } return wr; }
通過判斷ecb和type型別的具體內容,來決定建立什麼型別的WorkerRequest(上述型別的ISPAIWorkerRequest都繼承於HttpWorkerRequest),上面的程式碼可以看出對不同版本的IIS進行了不同的包裝,通過其Initialize方法來初始化一些基本的資訊(比如:contentType, querystring的長度,filepath等相關資訊)。
OK,繼續看ProcessRequest方法的加粗程式碼,激動人心的時刻來了,看到HttpRuntime.ProcessRequestNoDemand(wr)這行程式碼了麼?這就是真正進入了ASP.NET Runtime Pipeline的唯一入口,傳遞的引數是上面遮蔽了差異化以後的WorkerRequest物件例項。HttpRuntime.ProcessRequestNoDemand最終體現在呼叫ProcessRequestInternal方法上,讓我們來看看該方法都是做了什麼事情。
private void ProcessRequestInternal(HttpWorkerRequest wr) { // Construct the Context on HttpWorkerRequest, hook everything together HttpContext context; try { context = new HttpContext(wr, false /* initResponseWriter */); } catch { // If we fail to create the context for any reason, send back a 400 to make sure // the request is correctly closed (relates to VSUQFE3962) wr.SendStatus(400, "Bad Request"); wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8"); byte[] body = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>"); wr.SendResponseFromMemory(body, body.Length); wr.FlushResponse(true); wr.EndOfRequest(); return; } wr.SetEndOfSendNotification(_asyncEndOfSendCallback, context); // Count active requests Interlocked.Increment(ref _activeRequestCount); HostingEnvironment.IncrementBusyCount(); try { // First request initialization try { EnsureFirstRequestInit(context); } catch { // If we are handling a DEBUG request, ignore the FirstRequestInit exception. // This allows the HttpDebugHandler to execute, and lets the debugger attach to // the process (VSWhidbey 358135) if (!context.Request.IsDebuggingRequest) { throw; } } // Init response writer (after we have config in first request init) // no need for impersonation as it is handled in config system context.Response.InitResponseWriter(); // Get application instance IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context); if (app == null) throw new HttpException(SR.GetString(SR.Unable_create_app_object)); if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, app.GetType().FullName, "Start"); if (app is IHttpAsyncHandler) { // asynchronous handler IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app; context.AsyncAppHandler = asyncHandler; asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context); } else { // synchronous handler app.ProcessRequest(context); FinishRequest(context.WorkerRequest, context, null); } } catch (Exception e) { context.Response.InitResponseWriter(); FinishRequest(wr, context, e); } }
首先映入眼簾的是try/catch裡的HttpContext物件的例項化程式碼,這就是我們期待已久的全域性HttpContext物件產生的地方,引數依然是WorkerRequest的例項,HttpContext建構函式程式碼如下:
// ctor used in HttpRuntime internal HttpContext(HttpWorkerRequest wr, bool initResponseWriter) { _wr = wr; Init(new HttpRequest(wr, this), new HttpResponse(wr, this)); if (initResponseWriter) _response.InitResponseWriter(); PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_EXECUTING); }
我們又看到了2個驚喜的程式碼,HttpRequest和HttpResponse的例項化,通過對WorkerRequest和對HttpContext物件this引數的傳遞,將獲取各自需要的資訊,具體內部是怎麼判斷操作賦值的,我們就不仔細看了,另外再花2秒鐘看一下,catch裡面的程式碼,有我們經常看到的Bad Request頁面顯示的HTML程式碼組裝邏輯,也就是說如果HttpContext物件建立失敗的話,就會給我們顯示Bad Request頁面。
我們繼續更重要的程式碼,這又是另外一個入口,讓我們進入我們熟悉的HttpApplication,程式碼如下:
// Get application instance IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context);
通過HttpApplicationFactory的GetApplicationInstance靜態方法,獲取我們熟悉的HttpApplication物件例項,由於HttpApplication物件是繼承IHttpAsyncHandler,而IHttpAsyncHandler又繼承於IHttpHandler,所以上面app的型別是IHttpHandler是沒有錯的。繼續看後面的if (app is IHttpAsyncHandler)程式碼,就知道了app肯定走這裡的分支,然後執行呼叫asyncHandler.BeginProcessRequest方法了。
至此,HttpRuntime已經正式發揮其無可替代的作用了,也正式通過此物件正式進入了HttpApplication物件的建立以及大家熟知的HttpApplication以後的生命週期了。
參考資料:
http://dotnetslackers.com/articles/iis/ASPNETInternalsIISAndTheProcessModel2.aspx
http://msdn.microsoft.com/en-us/library/bb470252.aspx
http://msdn.microsoft.com/en-us/library/Aa479328.aspx
http://learn.iis.net/page.aspx/101/introduction-to-iis-architecture/
http://www.cnblogs.com/zhaoyang/archive/2011/11/16/2251200.html
http://www.dotnetfunda.com/articles/article821-beginners-guide-how-iis-process-aspnet-request.aspx
同步與推薦
本文已同步至目錄索引:MVC之前的那點事兒系列
MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。