回顧上一篇,我們可以瞭解到以下內容:
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運行時方面的知識。文章可能寫得並不完善,因為所尋找的資料在有些內容上有些小的出入,所以歡迎朋友們提出意見,
下一篇