Asp.Net 上傳大檔案專題(2)--頁面生成流程

iDotNetSpace發表於2009-06-03

回顧上一篇,我們可以瞭解到以下內容:

1.預設情況下,只能上傳小於4M的檔案,如果我們要上傳大檔案的話,可以通過更改maxRequestLength來提高限制。
2.Asp.net 1.X 通過改變maxRequestLength可以增大上傳的限制,但是由於需要將使用者請求的實體內容完全載入記憶體後再處理,會大大影響伺服器效能。
3.Asp.net 2.0 則會在使用者請求的實體內容超出一定閾值或稱限制值(256K)之後,被透明地緩衝到磁碟,因此在ASP.NET 2.0中伺服器的記憶體不會因為客戶端的異常請求而耗盡。

 

本篇概要:

      在這一篇中主要理清HTTP請求流從到達WEB伺服器開始到生成頁面所經歷的流程。為了能更好的理解本篇內空,建議大家先去看一下"HTTP請求流程"和"ASP.NET 應用程式生命週期";

 

正文內容:

      有些朋友可能會不耐煩了,“這和上傳大檔案有什麼關係呀?”。那是因為我們無法通過.Net提供給我們的上傳控制元件得到我們想要的效果,如果想實現我們上傳大檔案並顯示進度,那就只有在伺服器接受到HTTP請求後,對該請求進行處理。那我們就得對HTTP請求在伺服器端的流程有個瞭解。
      瀏覽器傳送頁面請求(包括Get、Post、Put等請求方式)到IIS伺服器後,在偵聽程式進行接收後,只有少數幾種被客戶端請求的資源型別由IIS 直接處理。例如,對 HTML 頁面、文字檔案、JPEG 和 GIF 影像的傳入請求由 IIS 處理。對 Active Server Page (*.asp) 檔案的請求通過呼叫名為 asp.dll 的 ASP 專用擴充套件模組進行解析。同樣,對 ASP.NET 資源(例如,*.aspx、*.asmx、*.ashx)的請求將傳遞到 ASP.NET ISAPI 擴充套件。因為IIS 6.0在IIS 5.x 上有所變動,所以我們分開來講。

1.先來看IIS 5.X 的 ASP.net 請求處理過程:


      由上圖可知,IIS 5.X 中偵聽程式由IIS程式(inetinfo.exe)來實現,它除了可以用來接收HTTP訊息的功能外,而且直接把aspnet_isapi.dll(asp.net isapi擴充套件)寄宿在了該程式裡。IIS 接收到訊息後,檢查指令碼對映,然後呼叫 ASP.NET ISAPI 擴充套件,又由該擴充套件將請求和控制以及相關的所有資訊傳送給輔助程式aspnet_wp.exe(該輔助程式也是由asp.net isapi呼叫,並在該程式初始化時自動載入了.Net 執行時)中的.Net 執行時。因為ASP.NET ISAPI 擴充套件和.Net執行時不屬於一個程式,所有的請求資料都通過命名管道進行傳送。

2.接著我們再來看一下IIS 6.0 的 ASP.net 請求處理過程:

      由圖可知,在IIS 6.0中,inetinfo.exe不再用來傳遞HTTP請求到ISAPI擴充套件,但繼續為其他協議的請求提供服務,取而代之的是採用名為 HTTP.sys 的Windows核心模式裝置驅動程式實現,它並不處理它所接收到的請求而是將偵聽到的HTTP 請求傳送到正在執行網站的使用者模式程式中(這裡不再是aspnet_wp.exe,而是名為W3wp.exe 的可執行檔案),在該輔助程式中載入了相應的ISAPI和.Net 執行時,這樣就便於ISAPI模組與.Net 執行時環境的資料通訊,從而避免了因程式間通訊所帶來的損耗。

 

      那麼,當HTTP請求由ASP.NET ISAPI 擴充套件傳送到.Net 執行時後,發生了什麼事呢?

      首先由ISAPIRuntime例項呼叫ProcessRequest方法。這個方法接收一個ECB(非託管物件,其中包含著所有底層的請求資訊如伺服器變數,HTTP輸入流,HTTP輸出流等)和一個伺服器型別引數 iWRType(這個引數用於指定建立何種版本的ISAPIWorkerRequest),然後把它傳給了ISAPIWorkerRequest物件,這個物件負責建立一個表示當前請求的HttpWorkerRequest物件。其中ISAPIWorkerRequest是一個繼承自HttpWorkerRequest的抽象類,針對不同的IIS版本,具有不同的ISAPIWorkerRequest子類,比如:ISAPIWorkerRequestOutOfProc(IIS 5.x),ISAPIWorkerRequestInProcForIIS6 ,ISAPIWorkerRequestInProcForIIS7。ProcessRequest通過ISAPI傳入的 iWRType 來建立不同HttpWorkerRequest,從而遮蔽了不同IIS的差異,這樣後續的操作就不需要考慮這種差異了。

 

public int ProcessRequest(IntPtr ecb, int iWRType)
{
    int num;
    try
    {
        HttpWorkerRequest wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, iWRType);
        string appPathTranslated = wr.GetAppPathTranslated();
        string appDomainAppPathInternal = HttpRuntime.AppDomainAppPathInternal;
        if ((appDomainAppPathInternal == null) || StringUtil.EqualsIgnoreCase(appPathTranslated, appDomainAppPathInternal))
        {//這裡開始便把請求的處理流程就交給了HttpRuntime
            HttpRuntime.ProcessRequestNoDemand(wr);
        //在ProcessRequestNoDemand方法中所有的HTTP請求會被排成一個佇列,順次執行,並且最終會引發ProcessRequestInternal(HttpWorkerRequest wr)方法
            return 0;
        }
        HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString("Hosting_Phys_Path_Changed", new object[] { appDomainAppPathInternal, appPathTranslated }));
        num = 1;
    }
    catch (Exception exception)
    {
        Misc.ReportUnhandledException(exception, new string[] { SR.GetString("Failed_to_process_request") });
        throw;
    }
    return num;
}
 
         由上面的程式碼中大家可以看出最終是呼叫了ProcessRequestInternal方法,這個方法很重要,我們來看一下它主要做了些什麼。

 

ProcessRequestInternal
private void ProcessRequestInternal(HttpWorkerRequest wr)
{
    HttpContext extraData = new HttpContext(wr, false);
    wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, extraData);
    Interlocked.Increment(ref this._activeRequestCount);
    HostingEnvironment.IncrementBusyCount();
    try
    {
        try
        {
            this.EnsureFirstRequestInit(extraData);
        }
        catch
        {
            if (!extraData.Request.IsDebuggingRequest)
            {
                throw;
            }
        }
        extraData.Response.InitResponseWriter();
        IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(extraData);
        if (applicationInstance == null)
        {
            throw new HttpException(SR.GetString("Unable_create_app_object"));
        }
        if (EtwTrace.IsTraceEnabled(5, 1))
        {
            EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, extraData.WorkerRequest, applicationInstance.GetType().FullName, "Start");
        }
        if (applicationInstance is IHttpAsyncHandler)
        {
            IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
            extraData.AsyncAppHandler = handler2;
            handler2.BeginProcessRequest(extraData, this._handlerCompletionCallback, extraData);
        }
        else
        {
            applicationInstance.ProcessRequest(extraData);
            this.FinishRequest(extraData.WorkerRequest, extraData, null);
        }
    }
    catch (Exception exception)
    {
        extraData.Response.InitResponseWriter();
        this.FinishRequest(wr, extraData, exception);
    }
}
 

      1. 首先檢查當前HttpRuntime例項是否第一次被呼叫,如果是第一次呼叫則通過FirstRequestInit函式建立並初始化核心物件HttpContext。
          HttpContext 類包含特定於當前應用程式請求的物件,如 HttpRequest 和 HttpResponse 物件。HttpRequest 物件包含有關當前請求的資訊,包括 Cookie 和瀏覽器資訊。HttpResponse 物件包含傳送到客戶端的響應,包括所有呈現的輸出和 Cookie。    
      2.呼叫HttpResponse.InitResponseWriter函式初始化頁面請求的返回物件HttpWorkerRequest.Response。

      3.通過呼叫HttpApplicationFactory.GetApplicationInstance建立HttpApplication 類的例項並最終呼叫HttpApplication例項的InitInternal方法啟動應用程式。
    
      HttpApplication例項在InitInternal呼叫後,首先初始化Web.Config中註冊的所有模組(HttpModule事件處理器,這個大家要尤其記住,因為我們真正開始編寫程式碼的話,主要就是在這個模組中編寫),然後呼叫HttpApplication 類的 Init 方法。接著會依次引發以下事件。

 

1.引發 BeginRequest 事件,該事件始終是請求處理期間發生的第一個事件。
2.引發 AuthenticateRequest 事件。
3.引發 PostAuthenticateRequest 事件。
4.引發 AuthorizeRequest 事件。
5.引發 PostAuthorizeRequest 事件。
6.引發 ResolveRequestCache 事件。
7.引發 PostResolveRequestCache 事件。
8.根據所請求資源的副檔名(在應用程式的配置檔案中對映),選擇實現 IHttpHandler 的類,對請求進行處理。如果該請求針對從 Page 類派生的物件(頁),並且需要對該頁進行編譯,則 ASP.NET 會在建立該頁的例項之前對其進行編譯。
9.引發 PostMapRequestHandler 事件。
10.引發 AcquireRequestState 事件。
11.引發 PostAcquireRequestState 事件。
12.引發 PreRequestHandlerExecute 事件。
13.為該請求呼叫合適的 IHttpHandler 類的 ProcessRequest 方法(或非同步版 BeginProcessRequest)。例如,如果該請求針對某頁,則當前的頁例項將處理該請求。
14.引發 PostRequestHandlerExecute 事件。 15.引發 ReleaseRequestState 事件。
16.引發 PostReleaseRequestState 事件。
17.如果定義了 Filter 屬性,則執行響應篩選。
18.引發 UpdateRequestCache 事件。
19.引發 PostUpdateRequestCache 事件。
20.引發 EndRequest 事件。

        由上面第8個事件可以看出,頁面在這個步驟被編譯並建立了當前所請求的ASP.NET頁面的例項(如果已經編譯過,直接從快取中載入)。

 

最後再來回顧一下:
 

1.首先由inetinfo.exe接收到HTTP請求

2.檢查指令碼對映,然後呼叫 ASP.NET ISAPI 擴充套件

3.將訊息送入aspnet_wp.exe程式,並載入執行時

4.呼叫ISAPIRuntime.ProcessRequest方法建立HttpWorkerRequest物件

6.建立HttpContext請求上下文

7.建立HttpApplication 類的例項

8.初始化Web.Config中註冊的所有模組(HttpModule)

9.呼叫HttpApplication 類的 Init 方法

10.觸發事件,例項化頁面


      由於知識點掌握不夠,寫這篇文章花了很長時間,但也讓我深入地學習了下.Net執行時方面的知識。文章可能寫得並不完善,因為所查詢的資料在有些內容上有些小的出入,所以歡迎朋友們提出意見,

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-605015/,如需轉載,請註明出處,否則將追究法律責任。

相關文章