1. 程式人生 > >MVC之前的那點事兒系列(3):HttpRuntime詳解分析(下)

MVC之前的那點事兒系列(3):HttpRuntime詳解分析(下)

文章內容

話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫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以後的生命週期了。

參考資料:

同步與推薦

MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。

相關推薦

MVC之前事兒系列3HttpRuntime分析

文章內容 話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime繼承了IISPAIRuntime介面,該介面可以和COM進行互動,並且暴露了ProcessRequest介面方法)。至於為什麼要呼叫這個方法,

MVC之前事兒系列2HttpRuntime分析

文章內容 從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖:   首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerApp

MVC之前事兒系列8UrlRouting的理解

文章內容 根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程式可以有多個HttpModuel,但是隻能有一個HttpHandler,並且通過這個HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前

MVC之前事兒系列1:進入CLR

MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3原始碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各型別文章,當然也參考了部落格園多位大牛的文章,對此表示感謝,這次有時間貼出來,希望對大家有用。 主要內容 本文講解的是:伺服器接受H

MVC之前事兒系列7WebActivator的實現原理

文章內容 上篇文章,我們分析如何動態註冊HttpModule的實現,本篇我們來分析一下通過上篇程式碼原理實現的WebActivator類庫,WebActivator提供了3種功能,允許我們分別在HttpApplication初始化之前,之後以及ShutDown的時候分別執行指定的程式碼,示例如下: [

MVC之前事兒系列9MVC如何在Pipeline中接管請求的?

文章內容 上個章節我們講到了,可以在HttpModules初始化之前動態新增Route的方式來自定義自己的HttpHandler,最終接管請求的,那MVC是這麼實現的麼?本章節我們就來分析一下相關的MVC原始碼來驗證一下我們的這個問題。 先建立一個MVC3的Web Application,選擇預設的模

MVC之前事兒系列4Http Pipeline詳細分析

文章內容 繼續上一章節的內容,通過HttpApplicationFactory的GetApplicationInstance靜態方法獲取例項,然後執行該例項的BeginProcessRequest方法進行執行餘下的Http Pipeline 操作,程式碼如下: // Get application i

MVC之前事兒系列5Http Pipeline詳細分析

文章內容 接上面的章節,我們這篇要講解的是Pipeline是執行的各種事件,我們知道,在自定義的HttpModule的Init方法裡,我們可以新增自己的事件,比如如下程式碼: public class Test : IHttpModule { public void Init(HttpAp

MVC之前事兒系列6動態註冊HttpModule

文章內容 通過前面的章節,我們知道HttpApplication在初始化的時候會初始化所有配置檔案裡註冊的HttpModules,那麼有一個疑問,能否初始化之前動態載入HttpModule,而不是隻從Web.config裡讀取? 答案是肯定的, ASP.NET MVC3釋出的時候提供了一個Microsof

MVC之前事兒系列10MVC為什麼不再需要註冊萬用字元*.*了?

文章內容 很多教程裡都提到了,在部署MVC程式的時候要配置萬用字元對映(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前確實應該這麼多,但是.NET4.0之後已經不要再費事了,因為它預設就支援了。 你可以會問,沒有對映配置,請求這麼可能會走到aspnet_ISPAI.dll

MVC之前事兒 ---- 系列文章

需要 cnblogs post omx pip 實現原理 內容 activator div MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3源碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各類型文

解讀ASP.NET 5 & MVC6系列6Middleware

在第1章專案結構分析中,我們提到Startup.cs作為整個程式的入口點,等同於傳統的Global.asax檔案,即:用於初始化系統級的資訊(例如,MVC中的路由配置)。本章我們就來一一分析,在這裡如何初始化這些系統級的資訊。 新舊版本之間的Pipeline區別 ASP.NET 5和之前版本的最大區別是對HT

Android專案之JSON解析3種解析技術

前言: 在我寫部落格前再宣告一下,我希望轉載我文章的某某某記得註明:(),要尊重我的勞動成果,這樣才能給我更多的支援和鼓勵!差不多有3天沒有寫部落格了,要想的、要做的事情太多了,額....原歸正傳,今天接著上一篇部落格:Android專案之JSON解析(扯淡),繼續分享我對

SpringBoot 2.0 系列流程

寫在前面 本節將詳細介紹如何使用Spring Boot。它涵蓋了諸如專案管理及自動構建工具、自動配置以及如何執行應用程式等主題。我們還介紹了一些Spring Boot最佳實踐。Spring Boot沒有什麼特別之處(它只是另一個我們可以使用的庫),但是有一些約

前端基礎進階圖例道setTimeout與迴圈閉包的經典面試題

配圖與本文無關 我在詳細圖解作用域鏈與閉包一文中的結尾留下了一個關於setTimeout與迴圈閉包的思考題。 利用閉包,修改下面的程式碼,讓迴圈輸出的結果依次為1, 2, 3, 4, 5 for (var i=1; i<=5; i++) { setTimeout( function ti

【轉】《與MySQL的零距離接觸》第三章約束以及修改資料表 3-7MySQL 修改資料表–刪除約束

3-7:MySQL 修改資料表–刪除約束 一. 前言 上一節最後我們講到了刪除預設約束,本節我們來講解刪除主鍵約束和唯一約束以及外來鍵約束 二. 刪除主鍵約束 刪除主鍵約束的語法結構: ALTER TABLE tbl_name DROP PRI

算法筆記復雜度分析

n+1 增長 角度 復雜 判斷 and 就是 ret 執行時間 (一)漸進符號(這裏暫時只考慮大O) 以輸入規模n為自變量建立的時間復雜度實際上還是較復雜的,例如an2+bn+c+1,不僅與輸入規模有關,還與系統a、b和c有關。此時對該函數進一步抽象,僅考慮運行時間的

java基礎學習總結二十五logback

為什麼使用logback logback大約有以下的一些優點: 核心重寫、測試充分、初始化記憶體載入更小,這一切讓logback效能和log4j相比有諸多倍的提升 logback非常自然地直接實現了slf4j,這個嚴格來說算不上優點,只是這樣,再理解slf4j的前提下會很容易理解

Dubbo概念篇Dubbo ,架構演變及優缺點

架構演變 單一應用框架(ORM) 當網站流量很小時,只需一個應用,將所有功能如下單支付等都部署在一起,以減少部署節點和成本。 缺點:單一的系統架構,使得在開發過程中,佔用的資源越來越多,而且隨著流量的增加越來越難以維護。 垂直應用框架(MVC) 垂直應用架構解決了單一應用架

C語言面向物件程式設計繼承

    C++ 中的繼承,從派生類與基類的關係來看(出於對比 C 與 C++,只說公有繼承): 派生類內部可以直接使用基類的 public 、protected 成員(包括變數和函式) 使用派生類的物件,可以像訪問派生類自己的成員一樣訪問基類的成員  對於被派生