ASP.NET是如何在IIS下工作的

風的姿態發表於2014-04-16

ASP.NET與IIS是緊密聯絡的,由於IIS6.0與IIS7.0的工作方式的不同,導致ASP.NET的工作原理也發生了相應的變化。

 

IIS6(IIS7的經典模式)與IIS7的整合模式的不同

IIS6的執行過程:

IIS6執行圖示

分析上圖可知:

    在 User Mode 下,http.sys 接收到 http request,然後它會根據 IIS 中的 Metabase 檢視基於該 Request 的 Application 屬於哪個 Application Pool, 如果該 Application Pool 不存在,則建立之。否則直接將 request 發到對應 Application Pool 的 Queue中。每個 Application Pool 對應著一個 Worker Process — w3wp.exe,(執行在 User Mode 下)。

    在 IIS Metabase 中維護著 Application Pool 和 Worker Process 的Mapping。WAS(Web Administrative Service)根據這樣一個 mapping,將存在於某個 Application Pool Queue 的 request 傳遞到對應的 Worker Process (如果沒有,就建立這樣一個程式)。在 Worker Process 初始化的時候,載入 ASP.NET ISAPI,ASP.NET ISAPI 進而載入 CLR。最後通過 AppManagerAppDomainFactory 的 Create 方法為 Application 建立一個 Application Domain;通過 ISAPIRuntime 的  ProcessRequest 處理 Request,進而將流程進入到 ASP.NET Http Runtime Pipeline。

   幾個知識點:

  • HTTP.SYS:(Kernel)的一個元件,它負責偵聽(Listen)來自於外部的HTTP請求,根據請求的URL將其轉發給相應的應用程式池 (Application Pool)。當此HTTP請求處理完成時,它又負責將處理結果傳送出去.為了提供更好的效能,HTTP.SYS內部建立了一個緩衝區,將最近的HTTP請求處理結果儲存起來。
  • Application Pool:  IIS總會保持一個單獨的工作程式:應用程式池。所有的處理都發生在這個程式裡,包括ISAPI dll的執行。對於IIS6而言,應用程式池是一個重大的改進,因為它們允許以更小的粒度控制一個指定程式的執行。你可以為每一個虛擬目錄或者整個Web 站點配置應用程式池,這可以使你很容易的把每一個應用程式隔離到各自的程式裡,這樣就可以把它與執行在同一臺機器上其他程式完全隔離。從Web處理的角度看,如果一個程式死掉,至少它不會影響到其它的程式。
    當應用程式池接收到HTTP請求後,交由在此應用程式池中執行的工作者程式Worker Process: w3wp.exe來處理此HTTP請求。
  • Worker Process: 當工作者程式接收到請求後,首先根據字尾找到並載入對應的ISAPI擴充套件 (如:aspx 對應的對映是aspnet_isapi.dll),工作者程式載入完aspnet_isapi.dll後,由aspnet_isapi.dll負責載入 ASP.NET應用程式的執行環境即CLR (.NET Runtime)。
    Worker Process執行在非託管環境,而.NET中的物件則執行在託管環境之上(CLR),它們之間的橋樑就是ISAPI擴充套件。
  • WAS(Web Admin Service):這是一個監控程式,它一方面可以存取放在InetInfo後設資料庫(Metabase)中的各種資訊,另一方面也負責監控應用程式池(Application Pool)中的工作者程式的工作狀態況,必要時它會關閉一個老的工作者程式並建立一個新的取而代之。

IIS7的執行過程:

     IIS7執行圖示

分析上圖可知:

    1、當客戶端瀏覽器開始 HTTP 請求一個WEB 伺服器的資源時,HTTP.sys 攔截到這個請求。

    2、HTTP.sys 聯絡 WAS 獲取配置資訊。

    3、WAS 向配置儲存中心(applicationHost.config)請求配置資訊。

    4、WWW 服務接收到配置資訊,配置資訊指類似應用程式池配置資訊,站點配置資訊等等。

    5、WWW 服務使用配置資訊去配置 HTTP.sys 處理策略。

    6、WAS為請求建立一個程式(如果不存在的話)。

    7、工作者程式處理請求並對HTTP.sys做出響應。

    8、客戶端接受到處理結果資訊。

 

除了IIS的整體執行方式不同之外,IIS7相比IIS6最大的不同之處在於它提供了兩種應用程式池管道模式:

經典模式:是與IIS 6或者之前版本保持相容的一種模式,一個典型問題就是,在處理ASP.NET這種動態網站的時候,它是通過一個所謂的ISAPI程式,作為外掛的方式來工作的。針對不同的動態應用程式(例如ASP,PHP等),會需要不同的ISAPI(Internet Server Application Programe Interface,網際網路伺服器應用程式介面)。如圖,在IIS中,開啟“處理程式對映”,可以看到aspx型別頁面的處理程式為aspnet_isapi.dll。

經典模式處理程式對映

下圖展示了IIS7經典模式與IIS6的應用程式池管道模式執行原理,針對不同的請求,會指定不同的ISAPI(dll)進行處理:

經典模式執行圖

 

整合模式:asp.net不再像IIS6一樣只限定於aspnet_isapi.dll中,而是被解放出來,從IIS接收到HTTP請求開始,即進入asp.net的控制範圍,asp.net可以存在於一個請求在IIS中各個處理階段。允許我們將ASP.NET更好地與IIS整合,甚至允許我們在ASP.NET中編寫一些功能(例如Module)來改變IIS的行為(擴 展)。整合的好處是,不再通過ISAPI的方式,提高了速度和穩定性。至於擴充套件,則可以使得我們對於IIS,以及其他型別的請求有更多的控制。(例如,我 們希望靜態網頁也具備一些特殊的行為)。如圖

整合模式執行圖

 

 

如下圖在IIS7整合模式中,開啟處理程式對映,可以看到aspx型別頁面所對應的不再是一個dll,而是一個型別。

整合模式處理程式對映

 

總結與擴充套件:

對於處理ASP.NET應用程式而言,IIS6及IIS7的經典模式需要aspnet_isapi.dll來處理,而IIS7整合模式不需要aspnet_isapi.dll來處理,而可以直接根據副檔名找到相應的處理程式介面。例如aspx的處理程式是System.Web.UI.PageHandlerFactory型別。

 

介紹完IIS的工作原理,來看一下ASP.NET內部的執行機制。

首先看一下IIS處理模型:

image

 

上面介紹IIS工作原理時,已經介紹了從發起HTTP請求,到響應請求的過程,這裡主要介紹當請求到達.NET Runtime之後,.NET執行時所發生的一系列工作。

先看如下的.NET執行時工作序列圖:

.net執行時序列圖

 


1.HTTP請求進入Web伺服器後,首先由HTTP.SYS來判斷請求的頁面是否存在,如果存在的話將把請求資訊轉交給.NET Runtime。在這部分實際是完成兩個步驟,在將請求轉交給.NET Runtime的同時將請求資訊封存在HTTPWorkRequest類中供其它步驟呼叫。HttpWorkRequest類在以後的操作中至關重要,它第一次將Http請求資訊轉換為類資訊。
2.當請求到達.NET Runtime後,接下來的操作將會在託管環境中完成,這時請求就真正進入了.NET中,對請求資訊的操作是由.NET的底層類庫來實現。首先.NET Runtime將會針對請求資訊做兩個動作,一是準備HostingEnvironment;二是呼叫ApplicationManager類為HTTP請求動態的分配AppDomain,並把處理權交給AppDomain。
3.HTTP請求進入AppDomain後,將由物件ISAPIRuntime來接管,一方面經方法ProcessRequest()得到HttpWorkerRequest物件,另一方面由方法StartProcessing()生成HttpRuntime物件,接下來把處理權交給了HttpRuntime(HttpWorkerRequest物件將作為HttpRuntime方法中的引數被使用)。
4.HTTPRuntime接收到Http請求後,方法ProcessRequest處理請求。將對第1步中的HTTPWorkRequest類中的資訊進行操作,具體的實現由ProcessRequest方法實現。內部程式碼如下:

[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Medium)]
public static void ProcessRequest(HttpWorkerRequest wr)
{
    if (wr == null)
    {
        throw new ArgumentNullException("wr");
    }
    if (UseIntegratedPipeline)
    {
        throw new PlatformNotSupportedException(System.Web.SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", new object[] { "HttpRuntime.ProcessRequest" }));
    }
    ProcessRequestNoDemand(wr);
}
internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)
{
    RequestQueue queue = _theRuntime._requestQueue;
    if (queue != null)
    {
        wr = queue.GetRequestToExecute(wr);
    }
    if (wr != null)
    {
        CalculateWaitTimeAndUpdatePerfCounter(wr);
        wr.ResetStartTime();
        ProcessRequestNow(wr);
    }
}
internal static void ProcessRequestNow(HttpWorkerRequest wr)
{
    _theRuntime.ProcessRequestInternal(wr);
}

5.在HttpRunTime中經過一系列的驅動後,將會在ProcessRequestInternal方法中為Http請求分配應用程式。在這一步中還將建立HttpContext物件。

context = new HttpContext(wr, false); // 基於HttpWorkerRequest生成HttpContext
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context); // 得到HttpApplication
handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context); // 由HttpApplication處理請求

6.經過步驟5後HTTP請求資訊才由基本資訊轉交給了Asp.net中的各個物件。接下來的操作會觸發一些列的管道事件,這時的請求才真正轉到HttpModule和HttpHandler中。
接下來我們看看常說的管道事件的建立過程:

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.ValidatePathExecutionStep(app));
    if (flag)
    {
        steps.Add(new HttpApplication.UrlMappingsExecutionStep(app));
    }
    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(new HttpApplication.CallHandlerExecutionStep(app));  //呼叫HttpHandler
    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;
}

管道事件請求序列圖如下:

管道

相關文章