[Day04] 掌控HttpApplication物件建立 - HttpApplicationFactory

前言:

附上Asp.net執行請求流程圖.

瀏覽器請求IIS流程

在前一篇我們說到HttpRunTime會透過GetApplicationInstance來取得一個IHttpHandler對象.

今天跟著原始碼來了解到底回傳一個什麼IHttpHandler物件給HttpRunTime使用.

查看原始碼好站 Reference Source

HttpApplication物件

HttpApplication是整個ASP.NET基礎的核心。一個HttpApplication物件在某個時刻只能處理一個請求,只有完成對某個請求處理後,該HttpApplication才能用於後續的請求的處理。

所以ASP.NET利用物件程序池機制來建立或者取得HttpApplication物件。具體來講,當第一個Http請求抵達的時候,ASP.NET會一次建立多個HttpApplication物件,並將其置於池中,選擇其中一個物件來處理該請求。

而如果程序池中沒有HttpApplication物件,Asp.net會建立新的HttpApplication物件處理請求

HttpApplication物件處理Http請求整個生命週期是一個相對複雜的過程,在該過程的不同階段會觸發相應的事件。我們可以註冊相應的事件(如同上一篇介紹事件表)

下圖就是模擬HttpApplicationObjectPool樣子

HttpApplication

取得使用 HttpApplication物件 (GetApplicationInstance)

讓我們看看GetApplicationInstan方法做了什麼事情.

private static HttpApplicationFactory _theApplicationFactory = new HttpApplicationFactory();

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);
}

_theApplicationFactory是一個靜態物件

_theApplicationFactory呼叫三個方法EnsureInited,EnsureAppStartCalled,GetNormalApplicationInstance,讓我們一一來解析做了些什麼事情吧

HttpApplicationFactory 初始化 (EnsureInited方法)

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

HttpApplicationFactory.EnsureInited()方法檢查HttpApplicationFactory是否已經被初始化,如果沒有就呼叫HttpApplicationFactory.Init()進行初始化。

Init()中,先獲取網站下global.asax文件完整路徑(透過GetApplicationFile方法),最後呼叫CompileApplication()方法對global.asax進行編譯.

在EnsureInited方法

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

private void CompileApplication() {
    // Get the Application Type and AppState from the global file
    _theApplicationType = BuildManager.GetGlobalAsaxType();

    BuildResultCompiledGlobalAsaxType result = BuildManager.GetGlobalAsaxBuildResult();

    if (result != null) {
        if (result.HasAppOrSessionObjects) {
            GetAppStateByParsingGlobalAsax();
        }

        _fileDependencies = result.VirtualPathDependencies;
    }

    if (_state == null) {
        _state = new HttpApplicationState();
    }

    ReflectOnApplicationType();
}

ReflectOnApplicationType方法取得目前特別事件方法,並添加到相對應的MethodInfo成員上

會透過以下三類方法名稱去取方法資訊

  • Application_OnStart or Application_Start
  • Application_OnEnd or Application_End
  • Session_OnEnd or Session_End
取得這些資訊會提供EnsureAppStartCalled去呼叫Application_OnStart方法 
private void ReflectOnApplicationType() {
    ArrayList handlers = new ArrayList();
    MethodInfo[] methods;

    Debug.Trace("PipelineRuntime", "ReflectOnApplicationType");

    // get this class methods
    methods = _theApplicationType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
    foreach (MethodInfo m in methods) {
        if (ReflectOnMethodInfoIfItLooksLikeEventHandler(m))
            handlers.Add(m);
    }
    
    // get base class private methods (GetMethods would not return those)
    Type baseType = _theApplicationType.BaseType;
    if (baseType != null && baseType != typeof(HttpApplication)) {
        methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
        foreach (MethodInfo m in methods) {
            if (m.IsPrivate && ReflectOnMethodInfoIfItLooksLikeEventHandler(m))
                handlers.Add(m);
        }
    }

    // remember as an array
    _eventHandlerMethods = new MethodInfo[handlers.Count];
    for (int i = 0; i < _eventHandlerMethods.Length; i++)
        _eventHandlerMethods[i] = (MethodInfo)handlers[i];
}

Application_Start方法為什麼只會呼叫一次? (EnsureAppStartCalled)

HttpApplicationFactory.EnsureAppStartCalled方法建立一個HttpApplication物件並觸發Application_OnStart事件(執行Global.asax中的Application_Start(object sender, EventArgs e))

在處理完事件Application_OnStartHttpApplication物件會立即被回收掉,因為系統初始化只需要一次

但是其中GetSpecialApplicationInstance裡會對IIS7做一些特殊的事情這裡就不多提
private void EnsureAppStartCalled(HttpContext context) {
    if (!_appOnStartCalled) {
        lock (this) {
            if (!_appOnStartCalled) {
                using (new DisposableHttpContextWrapper(context)) {

                    WebBaseEvent.RaiseSystemEvent(this, WebEventCodes.ApplicationStart);

                    FireApplicationOnStart(context);
                }

                _appOnStartCalled = true;
            }
        }
    }
}

private void FireApplicationOnStart(HttpContext context) {
    if (_onStartMethod != null) {
        HttpApplication app = GetSpecialApplicationInstance();

        app.ProcessSpecialRequest(
                                    context,
                                    _onStartMethod,
                                    _onStartParamCount,
                                    this, 
                                    EventArgs.Empty, 
                                    null);

        RecycleSpecialApplicationInstance(app);
    }
}

 

在處理完事件Application_OnStart呼叫RecycleSpecialApplicationInstance回收HttpApplication物件

返回一個 HttpApplication 物件 (GetNormalApplicationInstance)

方法中主要做.

  1. 判斷_freeList集合中是否有可用HttpApplication物件(物件程序池中),如果沒有就利用HttpRuntime.CreateNonPublicInstance(_theApplicationType)透過反射建立一個新的HttpApplication返回(呼叫完IHttpHandler.ProcessRequst方法後會將這個物件存入_freeList中),最後將
private HttpApplication GetNormalApplicationInstance(HttpContext context) {
    HttpApplication app = null;

    if (!_freeList.TryTake(out app)) {
        // If ran out of instances, create a new one
        app = (HttpApplication)HttpRuntime.CreateNonPublicInstance(_theApplicationType);

        using (new ApplicationImpersonationContext()) {
            app.InitInternal(context, _state, _eventHandlerMethods);
        }
    }

    if (AppSettings.UseTaskFriendlySynchronizationContext) {
        // When this HttpApplication instance is no longer in use, recycle it.
        app.ApplicationInstanceConsumersCounter = new CountdownTask(1); // representing required call to HttpApplication.ReleaseAppInstance
        app.ApplicationInstanceConsumersCounter.Task.ContinueWith((_, o) => RecycleApplicationInstance((HttpApplication)o), app, TaskContinuationOptions.ExecuteSynchronously);
    }
    return app;
}

所以最終我們是返回一個HttpApplication物件來使用.

小結

今天我們學到

  1. IHttpHandler GetApplicationInstance(HttpContext context)其實是返回一個HttpApplication物件.
  2. EnsureAppStartCalled方法中呼叫FireApplicationOnStart方法動態建立一個HttpApplication物件,呼叫完Application_OnStart事件就回收掉並使用一個flag布林值代表已經呼叫過.
  3. 這個工廠會有一個 _freeList 集合來存取之前用過的HttpApplication物件,如果集合中沒有適合的HttpApplication物件就會使用反射返回一個新的HttpApplication並將他初始化.
  4. 所以HttpRuntime呼叫的是HttpApplication物件的ProcessRequest方法

下篇會跟大家介紹HttpApplication類別成員詳細資訊


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^