ASP.NET Process Model之二:ASP.NET Http Runtime Pipeline[上篇]
ASP.NET Process Model索引
- ASP.NET Process Model之一:IIS 和 ASP.NET ISAPI
- ASP.NET Process Model之二:ASP.NET Http Runtime Pipeline[上篇]
- ASP.NET Process Model之二:ASP.NET Http Runtime Pipeline[下篇]
相信大家都使用過ASP.NET進行過基于Web的應用開發,ASP.NET是什么?如果站在一個相對High Level的角度,我們可以這樣來定義ASP.NET:ASP.NET是一個基于Web的開發平臺,提供構建企業級應用所需的Service、Programming Model和Software的Infrastructure。如果我們以一個Low Level的角度來看,它本質上就是一個消息處理器:他接受IIS(確切地說應該是ASP.NET ISAPI)Forward的Http Request (我們可以看成是一個Request Message),經過一系列的處理,最終產生一個用戶希望的Response(這也是一個Message,對于.aspx Page來說是一個Html document,對于一個Web Service來說是一個Soap)。所以本篇文章的主要目的在于站在一個相對Low Level的角度介紹ASP.NET的整個Http Request Processing Model。我們訪問一個基于ASP.NET的資源,IIS是第一道屏障,在第一個部分我分別就IIS 5.x和IIS 6的差異介紹了IIS對Http Request的處理,今天我們來繼續后面的故事。
一、從Unmanaged Environment到Managed Environment
上一部分我們說到IIS收到一個基于ASP.NET資源文件的訪問,它會把Http Request交給一個ASP.NET ISAPI Extension處理。ASP.NET ISAPI 會加載CLR,從而創建一個托管的環境。ASP.NET ISAPI Extension定義在一個名為aspnet_isapi.dll中,aspnet_isapi.dll是一個純Native的、高效的Dll,也就是說,雖然ASP.NET ISAPI通過加載CLR創建一個托管的環境,但是ASP.NET ISAPI本省卻運行在一個Unmanaged的環境中。而我們的ASP.NET Application確是完全的Managed code,運行在一個Managed的環境中。要了解ASP.NET Http Runtime Pipeline這個純托管的Runtime,我們必須先了解從Unmanaged Environment到Managed Environment的這道橋梁。
上圖簡單表述了在IIS 6環境下,從非托管環境到托管環境的過程。從圖中我們可以看到,ASP.NET ISAPI運行在一個非托管環境之中。ASP.NET ISAPI經過系列COM級別的class調用(由于這些被調用的Class都是一個個undocumented class,所以要真正說清楚調用流程中每個具體的細節是一件很難的事情,而且也確實沒有很大的必要去挖掘它,因為具體的實現可能會經常變動,如果對此具有好奇心的朋友可以通過一些Tool,比如Reflector去仔細研究一下),最終的調用降臨到一個托管的、繼承自System.Web.Hosting.ISAPIRuntime類的對象上。ISAPIRuntime 是一個特殊的class,他實現了Interface System.Web.Hosting.IISAPIRuntime。下面是該Interface的定義。通過定義我們可以看到,這是一個基于COM的Interface,也就是說Caller可以通過COM的方式調用實現該Interface的Class的對象。在這里,這個最初的Caller就是ASP.NET ISAPI。從這里我們可以總結出:ASP.NET ISAPI通過調用System.Web.Hosting.ISAPIRuntime Instance的ProcessRequest方法,進而從非托管的環境進入了托管的環境。
public interface IISAPIRuntime
{
void StartProcessing();
void StopProcessing();
[return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
void DoGCCollect();
}
ISAPI ECB (Execution Control Block) & ISAPIWorkerRequest
通過System.Web.Hosting.IISAPIRuntime Interface中的ProcessRequest方法的Siganature,我們可以看出該方法包含兩個參數,其中一個是名為ecb的Unmanaged Pointer,另一個是useProcessModel。ECB全稱是Execution Control Block,在整個Http Request Processing過程中起著非常重要的作用,我們現在來簡單介紹一個ECB。
ISAPI顧名思義,就是實現了一些基于Internet Server的API。Aspnet_isapi.dll實現了這些API,對于IIS來說,它可以調用這些API進入托管的環境實現對ISAPIRuntime的調用,對于ISAPIRuntime來說,它需要調用ASP.NET ISAPI實現一些必要的功能,比如獲得Server Variable的數據,獲得通過Post Mehod傳回Server的數據;以及最終將Response的內容返回給ASP.NET ISAPI,并通過ASP.NET ISAPI返回到Client。一般地ISAPIRuntime不能直接調用ASP.NET ISAPI,而是通過一個對象指針實現對其的調用,這個對象就是ECB,ECB實現了對ISAPI的訪問。
還有一點特別需要強調的是,ISAPI對ISAPIRutime的調用是異步的,也就是說ISAPI調用ISAPIRutime之后立即返回。這主要是出于Performance和Responsibility考慮的,因為ASP.NET Application天生就是一個多線程的應用,為了具有更好的響應能力,異步操作是最有效的解決方式。但是這里就會有一個問題,我們知道我們對ASP.NET 資源的調用本質上是一個Request/Response的Message Exchange Pattern,異步調用往往意味著ISAPI將Request傳遞給ISAPIRuntime,將不能得到ISAPIRuntime最終生成的Response,這顯然是不能接受的。而ECB解決了這個問題,ISAPI在調用ISAPIRutime的ProcessRequest方法時會將自己對應的ECB的指針傳給它,ISAPIRutime不但可以將最終生成的Response返回給ISAPI,還能通過ECB調用ISAPI獲得一些所需的數據。
明白ECB是怎么回事之后,我們通過Reflector簡單了解一下ISAPIRutime的ProcessRequest的實現:
ProcessRequest
public int ProcessRequest(IntPtr ecb, int iWRType)
{
IntPtr zero = IntPtr.Zero;
if (iWRType == 2)
{
zero = ecb;
ecb = UnsafeNativeMethods.GetEcb(zero);
}
ISAPIWorkerRequest wr = null;
try
{
bool useOOP = iWRType == 1;
wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
wr.Initialize();
string appPathTranslated = wr.GetAppPathTranslated();
string appDomainAppPathInternal = HttpRuntime.AppDomainAppPathInternal;
if ((appDomainAppPathInternal == null) || StringUtil.EqualsIgnoreCase(appPathTranslated, appDomainAppPathInternal))
{
HttpRuntime.ProcessRequestNoDemand(wr);
return 0;
}
HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString("Hosting_Phys_Path_Changed", new object[] { appDomainAppPathInternal, appPathTranslated }));
return 1;
}
catch (Exception exception)
{
try
{
WebBaseEvent.RaiseRuntimeError(exception, this);
}
catch
{
}
if ((wr == null) || (wr.Ecb != IntPtr.Zero))
{
throw;
}
if (zero != IntPtr.Zero)
{
UnsafeNativeMethods.SetDoneWithSessionCalled(zero);
}
if (exception is ThreadAbortException)
{
Thread.ResetAbort();
}
return 0;
}
}
對于上面的代碼,我覺得沒有必要去深究,但是對于那些具有強烈好奇心的朋友除外J。基本上上面的代碼完成下面兩個任務:
通過傳入的ECB和iWRType創建一個叫做ISAPIWorkerRequest的對象:
wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
然后調用HttpRuntime.ProcessRequestNoDemand(wr),通過將創建的ISAPIWorkerRequest的對象作為參數傳入。
HttpRuntime.ProcessRequestNoDemand的調用真正進入了ASP.NET Runtime Pipeline,這是一個相對復雜的過程。在這里我想簡單說說ISAPIWorkerRequest這個重要class,ISAPIWorkerRequest是一個Abstract class,它已通過ECB創建基于當前Request的Context的信息,針對不同的IIS版本,具有不同的ISAPIWorkerRequest subclass,比如:ISAPIWorkerRequestOutOfProc(IIS 5.x), ISAPIWorkerRequestInProcForIIS6, ISAPIWorkerRequestInProcForIIS7。ProcessRequest通過ISAPI傳入的iWRType來創建不同HttpWorkerRequest,從而屏蔽了不同IIS的差異,后續的步驟就不需要考慮這種差異了,這是Abstract Factory的典型用法。