1. 程式人生 > >ASP.NET - 請求處理機制淺析

ASP.NET - 請求處理機制淺析

鏈接 span strong 優化 進行 沒有 可控 安全 進入

一些基本概念

1.進程( Process )
//在一定的內存中承載應用程序,一個進程的錯誤可能造成其它進程的崩潰

2.應用程序域(AppDomain)
//Net程序需要Clr進行托管以保障安全,AppDomain正是Clr創建的區塊,Clr利用進程,將其劃分為N塊邏輯分區
//這些分區就稱為AppDomain,一個進程可以被劃分成N個AppDomain。
//而.Net程序集通過CLR被載入到某個AppDomain中進行處理,也即AppDomain的作用是用來承載.Net應用程序的,其優勢在於以下幾點:
*.內存開銷
//一個AppDomain的內存開銷顯著低於進程,而且跨域訪問對象的內存開銷也要顯著低於跨進程訪問對象

*.內存控制
//在C#中AppDomain被表示為一個類型,它提供了卸載程序集的方法,
//而Clr正是利用AppDomain進行垃圾回收和對程序集的卸載,開發人員也可以利用AppDomain對.net程序進行內存控制
//一旦程序超載則可以手動AppDomain卸載程序集,卸載後再將程序集重新載入以便降低內存占用。
*.熱插拔
//在C#中AppDomain被表示為一個類型,它提供了加載程序集的方法
//把應用程序的某個分離的功能做成一個即插即用的插件,可隨時應對客戶需求的變更。
*.邊界隔離
//一個進程的錯誤可能帶來其它進程的崩潰,
//比如一臺服務器開10個進程掛上10個網站應用程序,一個錯誤就會導致10個網站全軍覆沒,
//而AppDomain是進程的分區,域的邊界隔離機制可以將多個.Net程序隔離,使它們相互獨立,

//也即把每個網站程序放在一個進程的10個域中,一個AppDomain出錯永遠不會影響到其它AppDomain,
//從而不會導致整個進程的崩潰。但可能多個域都需要引用某個公用的程序集,最典型的例子就是MSCorLib.dll。
//該程序集包含了基元類型等重要的.net類型,像這樣的程序集不需要跨域訪問,
//因為在CLR初始化時就會將這種程序集自動載入一個公共域中,這樣,所有的AppDomain都共享該程序集的類型。

3.HTTP通信接口( HTTP.SYS )
//Windows的核心組件,任何基於Windows平臺的應用程序都要通過它提供的API接口才能進行HTTP通信

4,應用程序池( Application Pools )

//提供給開發人員管理應用程序的工具,控制應用程序的運行,結束,內存分配,請求最大鏈接數等

5.工作者進程( w3wp.exe )
//Windows的進程,與應用程序池是相互為用的關系,池管理應用程序,
//而w3wp.exe承載應用程序的運行。兩者相互協作,控制應用程序的生命。
//工作者進程下可以劃分多個AppDomain域來承載多個web應用程序。
//既然工作者進程總是關聯一個應用程序池,
//所以你也可以這樣理解:程序池是一個進程,該進程從Cpu中劃分了一定的內存,web程序就被限制在程序池中,一個池子可以承載多個web程序的運行。

HTTP請求的整個流程

HTTP請求最早由HTTP.SYS接收,接著傳遞給IIS,請求進入IIS後,IIS會測試該請求所獲取的是靜態還是動態資源,而早期的web總是以靜態的html資源返回給客戶端,後來逐漸出現了動態資源,而IIS無法處理這種請求,所以需要一些擴展,通過把請求傳遞給擴展程序,由擴展程序去處理請求。

靜態資源

IIS直接取出html,jpg等資源後往客戶端發送。

CGI資源

基於CGI協議的早期通用網關接口,可處理擴展名為cgi的動態資源的請求,如果擴展名為CGI,則IIS會開啟cgi.exe進程,執行由c,vb,python,php編寫的可執行文件對請求進行處理。

擴展程序

通過IIS提供的配置,可以將擴展程序註冊到由IIS管理的擴展程序映射表中,非靜態資源的請求進入IIS後,IIS將查找擴展程序映射表,為了便於理解,可以視此表為鍵值對集合,key是動態資源的擴展名,value是處理請求的擴展程序。處理HTTP請求的常見擴展程序則有以下幾種:

1.asp.dll擴展程序,執行由asp編寫的可執行文件對請求進行處理。

2.FASTCGI擴展程序,這是CGI進化版的擴展程序,執行由c,vb,python,php編寫的可執行文件對請求進行處理。

3.aspnet_isapi.dll擴展程序,可處理各種動態資源的請求,最強。

搭建Clr運行時

IIS確認後綴名所對應的擴展程序後則會通知應用程序池,後者通知w3wp.exe處理Http請求,而w3wp.exe進程會先確認CLR是否已經在運行,如果沒有,則將aspnet_isapi.dll載入內存,由aspnet_isapi.dll負責搭建CLR(.Net運行時)。我們知道asp.net的程序集必須通過CLR的即時編譯才能轉化為cpu指令,才能夠處理來自客戶端請求的動態資源,所以工作者進程首先得把Clr運行時搭建出來。

創建應用程序域

Clr運行時搭建完成後,將開始創建應用程序域,( 此處需要註意,應用程序域使用的是延遲初始化機制,也即只有當第一個請求進入IIS並被有效傳遞給工作者進程後,域才會被創建。假如你手動關閉域以此來減輕內存開銷,又或者遭遇不可控的因素致使域被關閉,那麽當下一個請求被IIS傳遞給工作者進程後,域又會被重新初始化)。由Clr調用System.Web.Hosting命名空間下的AppDomainFactory的Create方法(.Net.20時代是調用AppManagerAppDomainFactory.Create)在當前工作者進程中創建出AppDomain對象和ISAPIRuntime對象,域創建完成後,工作者進程將把HTTP請求傳遞給處於域中的ISAPIRuntime對象,而該對象會調用自身的ProcessRequest方法,該方法根據原始的Http報文創建一個從HttpWorkerRequest派生的IsapiWorkerRequest對象,此對象就代表了Http請求的報文信息,接著該方法還會調用HttpRuntime的ProcessRequestNoDemand方法,該方法會調用其它方法,後續一系列的方法調用的邏輯都是為了能創建出HttpContext、HttpContext.Response、HttpContext.Request、HttpApplication、HttpModule和執行Http請求的管道事件,這些方法的部分源碼如下:

//HttpRuntime.ProcessRequestNoDemand的部分源碼 //此方法的作用:將Http請求報文對象添加到Http請求隊列中
internal static void ProcessRequestNoDemand( HttpWorkerRequest wr )
{
//RequestQueue是存儲IsapiWorkerRequest的隊列集合
//獲取Http請求報文的隊列集合
RequestQueue queue = _theRuntime._requestQueue;
//更新Http請求報文計數器,這將當前的Http請求報文添加到了集合中(個人臆測)
wr.UpdateInitialCounters( );
if (queue != null)
{
//再從隊列中獲取當前的Http請求報文
wr = queue.GetRequestToExecute( wr );
}
//ProcessRequestNow方法內部會將IsapiWorkerRequest參數傳遞給ProcessRequestInternal方法
ProcessRequestNow( wr );

}

//HttpRuntime.ProcessRequestInternal的部分源碼 //此方法的作用:根據Http請求的報文創建HttpContext、HttpContext.Response、HttpContext.Request對象、執行每一個註冊好的Http請求的管道事件
private void ProcessRequestInternal( HttpWorkerRequest wr )
{
HttpContext context;
//根據IsapiWorkerRequest創建HttpContext對象
//而在HttpContext的構造函數中會創建出Request和Response的實例
try { context = new HttpContext( wr, false ); }
catch
{
try
{
//400錯誤,請求無效
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;
}
}
//GetApplicationInstance方法內部會調用GetNormalApplicationInstance方法
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance( context );

if (applicationInstance is IHttpAsyncHandler)
{
//依次執行處理Http請求的管道事件
handler2.BeginProcessRequest( context, this._handlerCompletionCallback, context );
}
}

//HttpApplicationFactory.GetNormalApplicationInstance的部分源碼
//此方法的作用:創建HttpApplication對象 //此方法內部維護著一個HttpApplication池,從池中取出一個閑置狀態的HttpApplication或重新創建一個HttpApplication來處理Http請求
private HttpApplication GetNormalApplicationInstance( HttpContext context )
{
HttpContext state = null;
//如果HttpApplication池存在處於閑置狀態的HttpApplication則取出一個用於處理請求
if (this._numFreeAppInstances > 0)
{
state = (HttpApplication)this._freeList.Pop( );
//更新HttpApplication計數器
this._numFreeAppInstances--;
}
//如果池中沒有閑置的HttpApplication,則重新創建一個HttpApplication
else
{
state = (HttpApplication)HttpRuntime.CreateNonPublicInstance( this._theApplicationType );
}
//InitInternal方法內部會調用InitModules方法
state.InitInternal( context, this._state, this._eventHandlerMethods );
return state;
}

//HttpApplicationFactory.InitModules的部分源碼 //此方法的作用:初始化Http模塊(HttpModule)
private void InitModules( )
{
//讀取註冊在Web.config配置文件中的所有HttpModule節信息
HttpModuleCollection modules = RuntimeConfig.GetAppConfig( ).HttpModules.CreateModules( );
//HttpModule集合作為HttpApplicationFactory._moduleCollection的成員被使用
this._moduleCollection = modules;
//初始化所有HttpModule對象
this.InitModulesCommon( );
//註冊ASP.NET的19個用於處理Http請求的管道事件
this._stepManager.BuildSteps( this._resumeStepsWaitCallback );
}

  

關閉域:httpruntime.unloadappdomain

客戶端請求的動態資源的url原本是類似於以下形式
HTTP://Server_name/ISAPI.dll/File_name.aspx
經過IIS的優化,才變成了我們熟悉的方式
HTTP://Server_name/File_name.aspx

未證實

待續……

Visual Studio包含ISAPI向導加快開發

ASP.NET - 請求處理機制淺析