導讀:
相信大家都使用過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.ISAPIRuntimeInstance的ProcessRequest方法,進而從非托管的環境進入了托管的環境。
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("08a2c56f-7c16-41c1-a8be-432917a1a2d1")]
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);
}
null
try
{
bool uSEOOP = iWRType == 1
wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, uSEOOP);
wr.Initialize();
string appPathTranslated = wr.GetAppPathTranslated();
string 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);
}
{
}
if ((wr == null) || (wr.Ecb != IntPtr.Zero))
{
throw
}
if (zero != IntPtr.Zero)
{
UnsafeNativeMethods.SetDoneWithSessionCalled(zero);
}
if ThreadAbortException)
{
Thread.ResetAbort();
}
return 0
對於上面的代碼,我覺得沒有必要去深究,但是對於那些具有強烈好奇心的朋友除外J。基本上上面的代碼完成下面兩個任務:
通過傳入的ECB和iWRType創建一個叫做ISAPIWorkerRequest的對象:
bool uSEOOP = iWRType == 1
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
, ISAPIWorkerRequestInProcForIIS
7。ProcessRequest通過ISAPI傳入的
iWRType來創建不同HttpWorkerRequest,從而屏蔽了不同IIS的差異,後續的步驟就不需要考慮這種差異了,這是Abstract Factory的典型用法。
通過 ASP.Net Http Runtime Pipeline - Part II進入第二部分。
Reference:A low-level Look at the ASP.Net Architecture
ASP.Net Process Model [原創]ASP.NET Process Model之一:IIS 和 ASP.Net ISAPI
[原創]ASP.NET Process Model之二:ASP.Net Http Runtime Pipeline - Part I
[原創]ASP.NET Process Model之二:ASP.Net Http Runtime Pipeline - Part II