1. 程式人生 > >MVC之前的那點事兒系列(4):Http Pipeline詳細分析(上)

MVC之前的那點事兒系列(4):Http Pipeline詳細分析(上)

文章內容

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

// Get application instance 
IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context);

那GetApplicationInstance這個方法究竟做了啥呢?難道只是new一個新物件出來?感覺應該不像,那我們就來看看HttpApplicationFactory類的GetApplicationInstance靜態方法原始碼:

internal static IHttpHandler GetApplicationInstance(HttpContext context) { 
    if (_customApplication != null)
        return _customApplication; 
 
    // Check to see if it's a debug auto-attach request
    if (context.Request.IsDebuggingRequest) 
        return new HttpDebugHandler();

    _theApplicationFactory.EnsureInited();
 
    _theApplicationFactory.EnsureAppStartCalled(context);
 
    
return _theApplicationFactory.GetNormalApplicationInstance(context); }

裡面有3行程式碼我已經標記為粗體了,在解釋每行程式碼的具體作用之前,先看看_theApplicationFactory物件例項從哪裡來,通過檢視該欄位的宣告程式碼可以看到它是單例的實現。

// the only instance of application factory
private static HttpApplicationFactory _theApplicationFactory = new HttpApplicationFactory();

第一行粗體程式碼是執行,該例項的EnsureInited方法,這個方法會通過lock的方式呼叫Init方法(好處自然不用多說了吧),程式碼如下:

private void EnsureInited() {
    if (!_inited) {
        lock (this) { 
            if (!_inited) {
                Init(); 
                _inited = true; 
            }
        } 
    }
}

通過查詢 Init方法的程式碼以及其中2行如下程式碼裡的細節,我們可以得知,這2行程式碼主要是從global.asax獲取內容,然後進行編譯。

_appFilename = GetApplicationFile(); 
CompileApplication();

所以,HttpApplicationFactory._theApplicationFactory.EnsureInited()  的方法首先檢查HttpApplicationFactory是否被初始化,如果沒有,就通過HttpApplicationFactory.Init()進行初始化。在Init()中,先獲取global.asax檔案的完整路徑,然後呼叫CompileApplication()對global.asax進行編譯。

第2行粗體的EnsureAppStartCalled方法,最終會呼叫如下的私有方法FireApplicationOnStart,程式碼如下:

private void FireApplicationOnStart(HttpContext context) { 
    if (_onStartMethod != null) { 
        HttpApplication app = GetSpecialApplicationInstance();
 
        app.ProcessSpecialRequest(
                                    context,
                                    _onStartMethod,
                                    _onStartParamCount, 
                                    this,
                                    EventArgs.Empty, 
                                    null); 

        RecycleSpecialApplicationInstance(app); 
    }
}

通過程式碼我們能夠得知HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context)  建立特定的HttpApplication例項,觸發ApplicationOnStart事件,執行ASP.global_asax中的Application_Start(object sender, EventArgs e)方法。然後在處理完事件以後就立即被回收掉,因為系統初始化只需要一次,但是其中的GetSpecialApplicationInstance裡會對IIS7做一些特殊的事情,我們後面的章節會講到。

第3行的粗體程式碼是我們這裡要說的重點,它方法裡的程式碼如下:

private HttpApplication GetNormalApplicationInstance(HttpContext context) {
    HttpApplication app = null; 

    lock (_freeList) {
        if (_numFreeAppInstances > 0) {
            app = (HttpApplication)_freeList.Pop(); 
            _numFreeAppInstances--;
 
            if (_numFreeAppInstances < _minFreeAppInstances) { 
                _minFreeAppInstances = _numFreeAppInstances;
            } 
        }
    }

    if (app == null) { 
        // If ran out of instances, create a new one
        app = (HttpApplication)HttpRuntime.CreateNonPublicInstance(_theApplicationType); 
 
        using (new ApplicationImpersonationContext()) {
            app.InitInternal(context, _state, _eventHandlerMethods); 
        }
    }

    return app; 
}

如果在有空閒的HttpApplication例項,就直接用,如果沒有就新建立,然後呼叫InitInternal方法進行初始化相關的內容,最後返回該HttpApplication例項。

讓我們來看看HttpApplication的核心方法InitInternal裡都是幹了什麼事兒吧,先上程式碼,有點多,但是很值得:

internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers) { 
    Debug.Assert(context != null, "context != null");

    // Remember state
    _state = state; 

    PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES); 
 
    try {
        try { 
            // Remember context for config lookups
            _initContext = context;
            _initContext.ApplicationInstance = this;
 
            // Set config path to be application path for the application initialization
            context.ConfigurationPath = context.Request.ApplicationPathObject; 
 
            // keep HttpContext.Current working while running user code
            using (new DisposableHttpContextWrapper(context)) { 

                // Build module list from config
                if (HttpRuntime.UseIntegratedPipeline) {
 
                    Debug.Assert(_moduleConfigInfo != null, "_moduleConfigInfo != null");
                    Debug.Assert(_moduleConfigInfo.Count >= 0, "_moduleConfigInfo.Count >= 0"); 
 
                    try {
                        context.HideRequestResponse = true; 
                        _hideRequestResponse = true;
                        InitIntegratedModules();
                    }
                    finally { 
                        context.HideRequestResponse = false;
                        _hideRequestResponse = false; 
                    } 
                }
                else { 
                    InitModules();

                    // this is used exclusively for integrated mode
                    Debug.Assert(null == _moduleContainers, "null == _moduleContainers"); 
                }
 
                // Hookup event handlers via reflection 
                if (handlers != null)
                    HookupEventHandlersForApplicationAndModules(handlers); 

                // Initialization of the derived class
                _context = context;
                if (HttpRuntime.UseIntegratedPipeline && _context != null) { 
                    _context.HideRequestResponse = true;
                } 
                _hideRequestResponse = true; 

                try { 
                    Init();
                }
                catch (Exception e) {
                    RecordError(e); 
                }
            } 
 
            if (HttpRuntime.UseIntegratedPipeline && _context != null) {
                _context.HideRequestResponse = false; 
            }
            _hideRequestResponse = false;
            _context = null;
            _resumeStepsWaitCallback= new WaitCallback(this.ResumeStepsWaitCallback); 

            // Construct the execution steps array 
            if (HttpRuntime.UseIntegratedPipeline) { 
                _stepManager = new PipelineStepManager(this);
            } 
            else {
                _stepManager = new ApplicationStepManager(this);
            }
 
            _stepManager.BuildSteps(_resumeStepsWaitCallback);
        } 
        finally { 
            _initInternalCompleted = true;
 
            // Reset config path
            context.ConfigurationPath = null;

            // don't hold on to the context 
            _initContext.ApplicationInstance = null;
            _initContext = null; 
        } 
    }
    catch { // Protect against exception filters 
        throw;
    }
}

該程式碼主要有2個功能,一個是初始化大家熟悉的HttpModules,一個是通過BuildSteps執行20多個生命週期事件的處理函式(這部分內容,我們將在下一章節詳細講解Http Pipeline)。通過上面的程式碼我們可以看出,每個功能都有一個特殊判斷,判斷IIS是否是IIS7的整合模式,如果是就有特殊的步驟,如果不是就走一般的步驟,兩者直接的差異分別是:IIS7初始化HttpModules的時候會從網站配置的Modules裡讀取(因為IIS7預載入CLR和大批量Modules),BuildSteps的時候, IIS7整合模式走的是自己特殊的流程(載入伺服器上的HttpModules)。

讓我們先總結一下再看程式碼,InitInternal方法的主要功能如下:

  1. InitModules():根據Web.Config的設定,載入相應的HttpModules。
  2. InitIntegratedModules():會載入IIS7整合模式下在伺服器上設定的HttpModuels和Web.config裡system.webserver下的HttpModuels。
  3. HookupEventHandlersForAppplicationAndModules:根據發生的事件,呼叫HttpApplication例項中相應的事件處理函式。
  4. 建立很多實現IExecutionStep介面的類的例項並新增到當前HttpApplication例項的_execSteps中,等待回撥時執行。從這裡我們可以看到HttpApplication是以非同步的方式處理請求, 對請求的很多處理工作都放入了_execStep等待回撥時執行。

至此,除了20多個週期事件和Handler相關的程式碼我們沒有講解,其它和HttpApplication相關的並且對我們有幫助的,已經差不多清晰了。關於20多個週期事件和執行Handler方面的內容,我們下一章節再做詳細解釋。

參考資料:

同步與推薦

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

相關推薦

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之前事兒系列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之前事兒系列3HttpRuntime詳解分析

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

MVC之前事兒系列2HttpRuntime詳解分析

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

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之前的內容,包括了原創,翻譯,轉載,整理等各類型文

EventBus原始碼分析執行緒模型分析2.4版本

EventBus有四種執行緒模型 PostThread模式不需執行緒切換,直接在釋出者執行緒進行事件處理。 MainThread模式分類討論:釋出者執行緒是主執行緒則直接呼叫事件處理方法,否則通過Handler進行執行緒切換,切換到主執行緒處理事件,該模

算法筆記復雜度分析

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

Android單元測試的利器JuintJuint的詳細用法

前言 這幾天正在成都出差,欣賞著成都的妹紙。 當我開始寫這篇的時候是上週五,沒想到這麼快就星期二了,東西越寫越多,為了保持文章儘量短小精悍,Juint的詳細用法就分成多篇來寫把,具體能寫幾篇我也不清楚… 正文 Assertions(斷言) 斷言

三十歲以前不要在乎的事情- -轉自http://smallcar.bokee.com/3123902.html

三十歲以前不要在乎的事情- -                                             1.放棄 :把握的反面就是放棄,選擇了一個機會,就等於放棄了其它所有的可能。當新的機會擺在面前的時候,敢於放棄已經獲得的一切,這不是功虧一簣,這不是半途而廢

聽課筆記第五講 學習的可行性分析一些概念和思想 (臺灣國立大學機器學習基石

Training versus Testing1,回顧:學習的可行性?最重要的是公式: (1) 假設空間H有限(M),且訓練資料足夠大,則可以保證測試錯誤率Eout 約等於訓練錯誤率Ein;(2)如果能得到Ein 接近於零,根據(1),Eout 趨向於零。以上兩條保證的學習的可能性。可知,假設空間H 的

談談源碼管理事兒——源碼管理十誡

我不 evel .html 文件夾 jetbrains enable thum XML 構建 引言: 若是還有能夠毫無偏見地涉及各個編程語言。比源碼管理軟件更必要的工具。我倒是非常想見識一下。源碼管理軟件是我們工作的必備工具,是很多開發團隊的血液。那為什麽我們都

Maven 事兒

做到 conn active cep ant tab name www color 0. 前言 Jason Van Zyl,在 Java 十大風雲人物排行榜上或許會看到他。 這兄弟是幹嘛的? 他就是 Maven 的創始人,人們都尊稱他為“Maven 他爸&

資料分析事兒

在之前我們給大家講了講什麼是資料分析以及資料分析的目的,資料分析就是通過使用合適的方法進行統計,統計也不是隨隨便便的統計的,需要找對方法。統計分析方法對收集來的大量資料進行分析,提取有用資訊和形成結論而對資料加以詳細研究和概括總結的過程。而資料分析的目的就是通過分析資料找到企業未來的發展情況。今天就給大家

Maven事兒Eclipse版

前言:   由於最近工作學習,總是能碰到Maven的原始碼。雖然平時工作並不使用Maven,但是為了學習一些原始碼,還是必須要了解下。這篇文章不是一個全面的Maven解析,而是一個簡單的介紹,包括Eclipse中如何使用Maven,如何利用Maven工程學習。   循序漸進