ASP.Net請求處理機制初步探索之旅 - Part 2 核心

開篇:上一篇咱們瞭解了一個請求從客戶端發出到服務端接收並轉到ASP.Net處理入口的過程,這篇咱們開始探索ASP.Net的核心處理部分,藉助強大的反編譯工具,咱們會看到幾個熟悉又陌生的名詞(類):HttpRuntime、HttpWorkerRequest、HttpContext、HttpApplication等。html

(1)Part 1:前奏程序員

(2)Part 2:核心web

(3)Part 3:管道數組

(4)Part 4:WebForm頁面生命週期瀏覽器

(5)Part 5:MVC頁面聲命週期服務器

1、第一個入口:ISAPIRuntme.ProcessRequest()

  ISAPIRuntime是進入NET託管環境的入口,它在方法中經過一個ecb句柄指向了當前請求報文體的內存地址,將HTTP請求報文簡單封裝爲一個HttpWorkerRequest對象,而後就是各類咱們常常聽到的PR(ProcessRequest)方法了。網絡

①調用ISAPIRuntime對象的ProcessRequest方法進入ASP.NET處理流程

  經過Reflector,咱們能夠看到在ISAPIRuntime中的這個入口方法:ProcessRequestapp

②首先根據ecb句柄建立HttpWorkerRequest對象封裝原始請求報文

關於HttpWorkerRequest:ide

在Asp.Net中準備用於處理的請求,都必須封裝爲HttpWorkerRequest類型的對象,HttpWorkerRequest是一個抽象類型。這裏建立的是一個ISAPIWorkerRequest類型,它繼承於HttpWorkerRequest類。函數

[ComVisible(false)]
public abstract class HttpWorkerRequest
{
    // Fields
    private DateTime _startTime;
    private Guid _traceId;
    ......
}

  轉到ISAPIWorkerRequest類的CreateWorkerRequest方法中,看到首先判斷當前IIS服務器的版本(IIS6 or IIS7?),而後建立適合不一樣IIS的具體WorkerRequest對象,默認都是InProc進程內的,固然,也有OutOfProc進程外的。

internal static ISAPIWorkerRequest CreateWorkerRequest(IntPtr ecb, bool useOOP)
{
    if (useOOP)
    {
        EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero);
        if (EtwTrace.IsTraceEnabled(5, 1))
        {
            EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, false);
        }
        return new ISAPIWorkerRequestOutOfProc(ecb);
    }
    int num = UnsafeNativeMethods.EcbGetVersion(ecb) >> 0x10;
    if (num >= 7)
    {
        EtwTrace.TraceEnableCheck(EtwTraceConfigType.IIS7_ISAPI, ecb);
    }
    else
    {
        EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero);
    }
    if (EtwTrace.IsTraceEnabled(5, 1))
    {
        EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, true);
    }
    if (num >= 7)
    {
        return new ISAPIWorkerRequestInProcForIIS7(ecb);
    }
    if (num == 6)
    {
        return new ISAPIWorkerRequestInProcForIIS6(ecb);
    }
    return new ISAPIWorkerRequestInProc(ecb);
}

  因爲HttpWorkerRequest類封裝的請求報文很原始,很複雜,因此微軟沒有將其公開出來。

2、第二個入口:HttpRuntime.ProcessRequest()

  HttpRuntime是ASP.NET請求處理的第二個入口。當請求進來,首先進入HttpRuntime,由HttpRuntime來決定如何處理請求。默認狀況下,在machine.config和Web.config中並無顯式定義httpRuntime節點,但該節點是有默認值的,以下:

<httpRuntime 
   apartmentThreading="false"
   appRequestQueueLimit="5000"
   delayNotificationTimeout="5"
   enable="true"
   enableHeaderChecking="true"
   enableKernelOutputCache="true"
   enableVersionHeader="true"
   encoderType = "System.Web.Util.HttpEncoder"
   executionTimeout="110"
   maxQueryStringLength = "2048"
   maxRequestLength="4096"
   maxUrlLength = "260"
   maxWaitChangeNotification="0"
   minFreeThreads="8"
   minLocalRequestFreeThreads="4"
   relaxedUrlToFileSystemMapping = "False"
   requestLengthDiskThreshold="80"
   requestPathInvalidCharacters = "<,>,*,%,&,:,\"
   requestValidationMode = "4.0"
   requestValidationType = "System.Web.Util.RequestValidator"
   requireRootedSaveAsPath="true"
   sendCacheControlHeader="true"
   shutdownTimeout="90"
   useFullyQualifiedRedirectUrl="false"
   waitChangeNotification="0" />
View Code

  一般狀況下,咱們能夠在Web.config中更改httpRuntime節點的默認值,以下:

<configuration>
  <system.web>
  <httpRuntime maxRequestLength="4000"
    enable = "True"
    requestLengthDiskThreshold="512
    useFullyQualifiedRedirectUrl="True"
    executionTimeout="45"
    versionHeader="1.1.4128"/>
  </system.web>
</configuration>
View Code

①其次執行HttpRuntime的ProcessRequestNoDemand方法封裝HttpContext對象

  在HttpRuntime類中,有一個稱爲ProcessRequestNoDemand的靜態方法。這裏建立並初始化HttpWorkerRequest對象後,調用了HttpRuntime的這個ProcessRequestNoDemand方法。因而,咱們轉到ProcessRequestNoDemand方法,能夠看到以下代碼:

internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)
{
    RequestQueue queue = _theRuntime._requestQueue;
    wr.UpdateInitialCounters();
    if (queue != null)
    {
        wr = queue.GetRequestToExecute(wr);
    }
    if (wr != null)
    {
        CalculateWaitTimeAndUpdatePerfCounter(wr);
        wr.ResetStartTime();
        ProcessRequestNow(wr);
    }
}

  該方法先從請求隊列中取出一個請求,而後更新請求的引用計數器的信息,而後再將HttpWorkerRequest對象傳入ProcessRequestNow方法來處理請求。

  這裏咱們還能夠看到_theRuntime這個字段,它是HttpRuntime類的一個靜態字段,在HttpRuntime的靜態構造函數中進行初始化。

public sealed class HttpRuntime
{
    // Fields
    ......
    private static HttpRuntime _theRuntime;
    ......
}

  再回到ProcessRequestNoDemand方法中,咱們看到最後是由ProcessRequestNow方法來接棒。因而,咱們轉到ProcessRequestNow方法再來看看:

internal static void ProcessRequestNow(HttpWorkerRequest wr)
{
    _theRuntime.ProcessRequestInternal(wr);
}

  咱們看到在這個方法中又調用了_theRuntime的實例方法:ProcessRequestInternal,仍是把HttpWorkerRequest對象做爲參數傳遞了過去。因而,咱們再轉到ProcessRequestInternal這個方法中,發現了一個重要的對象:HttpContext對象。

private void ProcessRequestInternal(HttpWorkerRequest wr)
{
    Interlocked.Increment(ref this._activeRequestCount);
    if (this._disposingHttpRuntime)
    {
        ......
    }
    else
    {
        HttpContext context;
        try
        {
            context = new HttpContext(wr, false);
        }
        catch
        {
            try
            {
                wr.SendStatus(400, "Bad Request");
                wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
                byte[] data = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
                wr.SendResponseFromMemory(data, data.Length);
                wr.FlushResponse(true);
                wr.EndOfRequest();
                return;
            }
            finally
            {
                Interlocked.Decrement(ref this._activeRequestCount);
            }
        }
        ...... 
    }
}

  在HttpContext的構造函數中,根據HttpWorkerRequest對象建立了HttpContext對象,這是一個重要的Http上下文對象,兩個重要類型的字段也隨之被初始化:HttpRequest對象和HttpResponse對象。

  相信你們在進行ASP.NET開發時,常用這兩個類型的實例。例如,咱們能夠經過HttpContext.Current獲取到這個實例,且該實例會在整個生命週期中存活,咱們經過它能夠獲取到一些經常使用對象,如Request,Response,Session 等。

②經過HttpApplicationFactory獲得一個具體的HttpApplication實例

  讓咱們再次回到HttpRuntimeProcessRequestInternal這個方法中,剛剛HttpContext對象被建立後,緊接着又幹了什麼事?讓咱們看看源碼:

private void ProcessRequestInternal(HttpWorkerRequest wr)
{
    Interlocked.Increment(ref this._activeRequestCount);
    if (this._disposingHttpRuntime)
    {
        ......
    }
    else
    {
        HttpContext context;
        ...... 
        IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context); ......if (applicationInstance is IHttpAsyncHandler)
        {
            IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
            context.AsyncAppHandler = handler2;
            handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
        }
        else
        {
            applicationInstance.ProcessRequest(context);
            this.FinishRequest(context.WorkerRequest, context, null);
        }
        ...... 
    }
}

   首先,咱們看到了一個很是熟悉的字眼:IHttpHandler,咱們的ashx、aspx不都是實現了這個IHttpHandler接口的嗎?因而,咱們來看看這句:IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context); 它是經過一個叫作HttpApplicationFactory的工廠類來獲取了一個HttpApplication的實例,並將HttpContext上下文對象做爲參數傳遞了進去。因而,懷着好奇心,咱們轉到這個方法內部去看看:

internal static IHttpHandler GetApplicationInstance(HttpContext context)
{
    ......
    _theApplicationFactory.EnsureInited();
    _theApplicationFactory.EnsureAppStartCalled(context);
    return _theApplicationFactory.GetNormalApplicationInstance(context); 
}

  原來,它是調用了一個實例方法:GetNormalApplicationInstance來獲取HttpApplication。因而,咱們再轉到這個方法的內部去看看:

private HttpApplication GetNormalApplicationInstance(HttpContext context)
{
    HttpApplication state = null;
    lock (this._freeList)
    {
        if (this._numFreeAppInstances > 0)
        {
            state = (HttpApplication) this._freeList.Pop();
            this._numFreeAppInstances--;
            if (this._numFreeAppInstances < this._minFreeAppInstances)
            {
                this._minFreeAppInstances = this._numFreeAppInstances;
            }
        }
    }
    if (state == null)
    {
        state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType);
        using (new ApplicationImpersonationContext())
        {
            state.InitInternal(context, this._state, this._eventHandlerMethods);
        }
    }
    ......
    return state;
}

  經過查看這段代碼,它首先維護着一個HttpApplication池(_freeList,本質上就是一個Stack棧),而後判斷可用的HttpApplication實例的數量(_numFreeAppInstances)是否大於0?若是存在可用的,則從池中出棧,而後將可用數量減1。最後,再判斷可用的數量是否小於最低限制的數量,若是小於那麼則將最低限制的數量設置爲目前可用的數量。

HttpApplication Pool

  那麼,若是目前HttpApplication池暫時沒有可用的實例呢?咱們看到了這一句:state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType); 經過此段代碼,新建了一個新的HttpApplication實例,經過繼續深刻查看,原來是經過反射的方式將Global文件所編譯後的類封裝出來一個HttpApplication實例。

補充之一:_theApplicationType是_theApplicationFactory.EnsureInited();中被賦值的

private void CompileApplication()
{
    this._theApplicationType = BuildManager.GetGlobalAsaxType();
    ......
}

補充之二:全局事件中例如Application_Start方法如何保證只執行一次?在_theApplicationFactory.EnsureAppStartCalled(context);方法中,判斷_appOnStartCalled標誌,若是是false則調用FireApplicationOnStart方法觸發Application_Start方法,而後更改_appOnStartCalled標誌。

private void EnsureAppStartCalled(HttpContext context)
{
    if (!this._appOnStartCalled)
    {
        lock (this)
        {
            if (!this._appOnStartCalled)
            {
                using (new DisposableHttpContextWrapper(context))
                {
                    ......
                    this.FireApplicationOnStart(context);
                }
                this._appOnStartCalled = true;
            }
        }
    }
}

3、第三個入口:HttpApplication.Init()

  在前兩個入口中,HttpApplication實例被建立,如今HttpApplication須要進行初始化請求處理管道,來分別處理ASP.Net WebForm或ASP.Net MVC等類型的頁面的響應操做。

①初始化HttpModules

  讓咱們再次回到HttpRuntime的ProcessRequestInternal這個方法中,剛剛HttpApplication實例被建立後,開始了一系列的初始化操做,以下圖所示,調用了其InitInternal方法進行了初始化。

  轉到InitInternal方法內部,發現調用了一個很是重要的方法:InitModules()。

internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers)
{
    this._state = state;
    PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES);
    try
    {
         ......
         this.InitModules();
         ......
if (HttpRuntime.UseIntegratedPipeline)
{
this._stepManager = new PipelineStepManager(this);
}
     else
     {
        this._stepManager = new ApplicationStepManager(this);
      }
      this._stepManager.BuildSteps(this._resumeStepsWaitCallback);
   }
catch
{
......
   }
}

  在InitModules這個方法中,首先經過讀取Web.config配置文件中關於HttpModule的信息,而後將其傳遞給HttpModule的集合,以下代碼所示:

private void InitModules()
{
    HttpModuleCollection modules = RuntimeConfig.GetAppConfig().HttpModules.CreateModules();
    HttpModuleCollection other = this.CreateDynamicModules();
    modules.AppendCollection(other);
    this._moduleCollection = modules;
    this.InitModulesCommon();
}

  而後,調用InitModulesCommon方法,遍歷上面這個_moduleCollection集合,分別對其每個HttpModule執行其對應的Init方法。

private void InitModulesCommon()
{
    int count = this._moduleCollection.Count;
    for (int i = 0; i < count; i++)
    {
        this._currentModuleCollectionKey = this._moduleCollection.GetKey(i);
        this._moduleCollection[i].Init(this);
    }
    this._currentModuleCollectionKey = null;
    this.InitAppLevelCulture();
}

註冊19個請求處理管道事件

  接上述操做以後,InitInternal方法內部還執行了這樣一句:this._stepManager.BuildSteps(this._resumeStepsWaitCallback); 它完成了19個請求處理管道事件的註冊工做。

internal override void BuildSteps(WaitCallback stepCallback)
{
    ArrayList steps = new ArrayList();
    HttpApplication app = base._application;
    bool flag = false;
    UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;
    flag = urlMappings.IsEnabled && (urlMappings.UrlMappings.Count > 0);
    steps.Add(new HttpApplication.ValidateRequestExecutionStep(app));
    steps.Add(new HttpApplication.ValidatePathExecutionStep(app));
    if (flag)
    {
        steps.Add(new HttpApplication.UrlMappingsExecutionStep(app));
    }
    
    /* 如下代碼完成19個事件的註冊 */
    app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);
    steps.Add(new HttpApplication.MapHandlerExecutionStep(app));
    app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);
    steps.Add(app.CreateImplicitAsyncPreloadExecutionStep());
    steps.Add(new HttpApplication.CallHandlerExecutionStep(app));
    app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps);
    steps.Add(new HttpApplication.CallFilterExecutionStep(app));
    app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps);
    app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps);
    this._endRequestStepIndex = steps.Count;
    app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps);
    steps.Add(new HttpApplication.NoopExecutionStep());
    this._execSteps = new HttpApplication.IExecutionStep[steps.Count];
    steps.CopyTo(this._execSteps);
    this._resumeStepsWaitCallback = stepCallback;
}

  從上面的代碼可知,ApplicationStepManager對象的BuildSteps方法被調用,完成HttpApplication 19個管道事件的註冊。這個方法很重要,它將建立各類HttpApplication.IExecutionStep保存到一個數組列表 _execSteps 中:

internal override void BuildSteps(WaitCallback stepCallback)
{
    .....
    this._execSteps = new HttpApplication.IExecutionStep[steps.Count];
    steps.CopyTo(this._execSteps);
    .....
}

  這樣作的目的在於:便於在後面的BeginProcessRequest方法內部調用ResumeSteps方法依次執行這些對象的Execute()方法,完成各個事件的執行。 

③開始依次處理請求處理管道中的各個事件

  讓咱們再返回到HttpRuntime中的ProcessRequestInternal方法中,HttpApplication實例已建立好,HttpModules已初始化,請求處理管道中的19個事件也已經註冊好,如今須要的只是一一調用HttpModule中各個事件對應的執行方法便可。

private void ProcessRequestInternal(HttpWorkerRequest wr)
{
            ......
            if (applicationInstance is IHttpAsyncHandler)
            {
                IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
                context.AsyncAppHandler = handler2;
                handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
            }
            ......
}

  在上述代碼中,經過執行BeginProcessRequest方法,觸發了ResumeSteps方法依次執行每一個請求處理管道事件,也就進入了咱們所說的「請求處理管道」中。

IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
    this._context = context;
    this._context.ApplicationInstance = this;
    this._stepManager.InitRequest();
    this._context.Root();
    HttpAsyncResult result = new HttpAsyncResult(cb, extraData);
    this.AsyncResult = result;
    if (this._context.TraceIsEnabled)
    {
        HttpRuntime.Profile.StartRequest(this._context);
    }
// 依次執行各個請求處理管道事件
this.ResumeSteps(null);
return result;
}

關於請求處理管道:

HttpApplication 採用處理管道的方法進行處理,將處理的過程分紅多個步驟,每一個步驟經過事件的形式暴露給程序員,這些事件按照固定的處理順序依次觸發,程序員經過編寫事件處理方法就能夠自定義每個請求的擴展處理過程。

對於 HttpApplication 來講,到 ASP.NET 4.0 版本,提供了19 個標準事件,以下圖所示:

  至於在請求處理管道中的細節,咱們在Part 3中再看,今天就到此爲止,謝謝!

4、核心過程總覽

①ISAPIRuntime->HttpWorkerRequest->HttpRuntime

1

②HttpRuntime->HttpContext->HttpApplication

2

③到目前爲止的整體流程概覽

  • 首先,咱們從本身的瀏覽器經過網絡訪問Web服務器
  • 當ASP.NET接收到第一個請求時,將會建立一個應用程序域,而後會建立一個宿主環境
  • 而後ASP.NET建立並初始化核心對象HttpContext、HttpRequest和HttpResponse
  • 而後建立HttpApplication對象的實例來啓動應用程序
  • 經過進入請求處理管道來處理具體的請求

參考資料

(1)Darren Ji,《ASP.NET MVC請求處理管道聲明週期的19個關鍵環節》:http://www.cnblogs.com/darrenji/p/3795661.html

(2)木宛城主,《ASP.NET那點鮮爲人知的事兒》:http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html

(3)Tony He,《ASP.NET請求處理機制》:http://www.cnblogs.com/cilence/archive/2012/05/28/2520712.html

(4)兩會的博客,《IIS是怎樣處理ASP.NET請求的》:http://www.cnblogs.com/hkncd/archive/2012/03/23/2413917.html

(5)wjn2000,《ASP.NET請求處理過程(IIS6)》:http://www.cnblogs.com/wjn2010/archive/2011/04/21/2024341.html

(6)農村出來的大學生,《ASP.NET網頁請求處理全過程(反編譯)》:http://www.cnblogs.com/poorpan/archive/2011/09/25/2190308.html

 

相關文章
相關標籤/搜索