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 Runtime Pipeline
現在我們真正進入ASP.NET管轄的范疇,下圖基本上囊括整個處理過程涉及的對象,接下來我們一起來討論這一系列的對象如何相互協作去處理Http Request,并最終生成我們所需的Http Response。
HttpContext
上面我們介紹了ISAPI在調用ISAPIRuntime的時候將對應的ISAPI ECB Pointer作為參數傳遞給了ProcessRequest方法,這個ECB pointer可以看成是托管環境和非托管環境進行數據交換的唯一通道,Server Variable和Request Parameter通過它傳入ASP.NET作為進一步處理的依據,ASP.NET最后生成的Response通過它傳遞給ISAPI,并進一步傳遞給IIS最終返回到Client端。
借助這個傳進來的ECB Pointer,我們創建了一個ISAPIWorkerRequest。ISAPIWorkerRequest作為參數傳入HttpRuntime.ProcessRequestNoDemand的調用。HttpRuntime.ProcessRequestNoDemand最終體現在調用ProcessRequestInternal。下面是真個方法的實現:
private void ProcessRequestInternal(HttpWorkerRequest wr)
{
HttpContext context;
try
{
context = new HttpContext(wr, false);
}
catch
{
wr.SendStatus(400, "Bad Request");
wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
wr.SendResponseFromMemory(bytes, bytes.Length);
wr.FlushResponse(true);
wr.EndOfRequest();
return;
}
wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, context);
Interlocked.Increment(ref this._activeRequestCount);
HostingEnvironment.IncrementBusyCount();
try
{
try
{
this.EnsureFirstRequestInit(context);
}
catch
{
if (!context.Request.IsDebuggingRequest)
{
throw;
}
}
context.Response.InitResponseWriter();
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
if (applicationInstance == null)
{
throw new HttpException(SR.GetString("Unable_create_app_object"));
}
if (EtwTrace.IsTraceEnabled(5, 1))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, applicationInstance.GetType().FullName, "Start");
}
if (applicationInstance is IHttpAsyncHandler)
{
IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
context.AsyncAppHandler = handler2;
handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
}
else
{
applicationInstance.ProcessRequest(context);
this.FinishRequest(context.WorkerRequest, context, null);
}
}
catch (Exception exception)
{
context.Response.InitResponseWriter();
this.FinishRequest(wr, context, exception);
}
}
對象上面的代碼沒有必要深究,我們只需要了解大體的執行流程就可以了,下面這一段偽代碼基本上體現整個執行過程:
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
首先通過創建的ISAPIWorkerRequest創建按一個HttpContext對象,隨后通過HttpApplicationFactory.GetApplicationInstance創建一個IHttpHandler對象(一般情況下就是一個HttpApplication對象)。
正如他的名字體現的,HttpContext體現當前Request的上下文信息,它的生命周期知道整個Request處理結束或者處理超時。通過HttpContext對象我們可以訪問屬于當前Request的一系列常用的對象:Server,Session,Cache,Application,Request,Response,Trace,User,Profile等等。此外我們可以認為將一些數據放在Items屬性中作為狀態管理的一種方式,不過這種狀態管理和其他一些常用的方式,比如Session,Cache,Application,Cookie等,具有根本性的不同之處是其生命周期僅僅維持在當前Request的Context中。
HttpApplication
就像其名稱體現的一樣,HttpApplication基本上可以看成是真個ASP.NET Application的體現。HttpApplication和置于虛擬根目錄的Gloabal.asax對應。通過HttpApplicationFactory.GetApplicationInstance創建一個基于Gloabal.asax的HttpApplication對象。在HttpApplicationFactory.GetApplicationInstance方法返回創建的HttpApplication對象之前,會調用一個名為InitInternal的內部方法,該方法會做一些列的初始化的操作,在這些初始化操作中,最典型的一個初始化方法為InitModules(),該方法的主要的目的就是查看Config中注冊的所有HttpModule,并根據配置信息加載相應的Assembly,通過Reflection創建對應的HttpModule,并將這些Module加到HttpApplication 的_moduleCollection Filed中。
HttpApplication本身并包含對Request的任何處理,他的工作方式是通過在不同階段出發不同Event來調用我們注冊的Event Hander。
下面列出了HttpApplication所有的Event,并按照觸發的時間先后順序排列:
- BeginRequest:
- AuthenticateRequest & Post AuthenticateRequest
- AuthorizeRequest & Post AuthorizeRequest
- ResolveRequestCache & Post ResolveRequestCache
- PostMapRequestHandler:
- AcquireRequestState & AcquireRequestState:
- PreRequestHandlerExecute & Post RequestHandlerExecute:
- ReleaseRequestState & Post ReleaseRequestState
- UpdateRequestCache & PostUpdateRequestCache
- EndRequest:
ASP.NET Application, AppDomain and HttpApplication
對于一個ASP.NET Application來說,一個Application和一個虛擬目錄相對應,那么是不是一個Application 對應著一個AppDomain呢?一個Application是否就唯一對應一個Httpapplication對象呢?答案是否定的。
我們首先來看看Application和HttpApplication的關系,雖然我們對一個Application的Request最終都由一個HttpApplication對象來承載。但不能說一個Application就唯一對應一個固定的HttpApplication對象。原因很簡單,ASP.NET天生具有多線程的特性,需要通過相應不同的Client的訪問,如果我們只用一個HttpApplication來處理這些并發的請求,會對Responsibility造成嚴重的影響,通過考慮到Performance的問題,ASP.NET對HttpApplication的使用采用Pool的機制:當Request到達,ASP.NET會現在HttpApplication Pool中查找未被使用的HttpApplication對象,如果沒有,則創建之,否則從Pool直接提取。對于Request處理完成的HttpApplication對象,不會馬上銷毀,而是把它放回到Pool中供下一個Request使用。
對于Application和AppDomain的關系,可能你會說一個Application肯定只用運行在一個AppDomain之中。在一般情況下這句話無可厚非,但是這卻忽略了一種特殊的場景:在當前Application正在處理Request的時候,我們把web.config以及其他一些相關文件修改了,而且這種改變是可以馬上被ASP.NET檢測到的,為了使我們的變動能夠及時生效,對于改動后的第一個Request,ASP.NET會為期創建一個新的AppDomain,而對于原來的AppDomain,也許還在處理修改前的Request,所有原來的Appdomain會持續到將原來的Request處理結束之后,所以對于一個Application,可能出現多個AppDomain并存的現象。
HttpModule
我們上面提到HttpApplication就是一個ASP.NET Application的體現,HttpApplication本身并不提供對Request的處理功能,而是通過在不同階段出發不同的Event。我們能做的只能是根據我們具體的需求將我們的功能代碼作為Event Handler注冊到需要的HttpApplication Event上面。注冊這些Event Handler,我們首先想到的肯定就直接在HttpApplication對應的Global.asax中定義我們的EventHandler好了。這是最直接的辦法,而且Global.asax提供一個簡潔的方式是我們的實現顯得簡單:不需要向一般注冊Event一樣將Delegate添加到對應的Event上面,而是直接通過方法名稱和對應的Event匹配的方式直接將對應的方法作為相關的Event Handler。比如Application_ AcquireRequestState就是AcquireRequestState Event handler。
但是這種方式在很多情況下卻達不到我們的要求,更多地,我們需要的是一種Plug-in的實現方式:我們在外部定義一些Request Processing的功能,需要直接運用到我們的Application之中。通過使用HttpModule封裝這些功能模塊,并將其注冊到我們的Application的發式可以很簡單的實現這種功能。
HttpModule實現了System.Web.IHttpModule interface,該Interface很簡單,僅僅有兩個成員:
public interface IHttpModule
{
// Methods
void Dispose();
void Init(HttpApplication context);
}
我們只要在Init方法中注冊相應的HttpApplication Event Handler就可以了:
public class BasicAuthCustomModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthenticateRequest +=
new EventHandler(this.OnAuthenticateRequest);
}
public void Dispose() { }
public void OnAuthenticateRequest(object source, EventArgs eventArgs)
{
}
}
所有的HttpModule同machine.config或者Web.config的httpModules Section定義,下面是Machine.config定義的所有httpModule。
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</httpModules>
但是HttpModule如何起作用的,我們來回顧一下上面一節介紹的:HttpApplicationFactory.GetApplicationInstance方法返回創建的HttpApplication對象之前,會調用一個名為InitInternal的內部方法,該方法會做一些列的初始化的操作,在這些初始化操作中,最典型的一個初始化方法為InitModules(),該方法的主要的目的就是查看Config中注冊的所有HttpModule,并根據配置信息加載相應的Assembly,通過Reflection創建對應的HttpModule,并將這些Module加到HttpApplication 的_moduleCollection Filed中,最后依次調用每個HttpModule的Init方法。下面是其實現:
{
this._moduleCollection = RuntimeConfig.GetAppConfig().HttpModules.CreateModules();
this.InitModulesCommon();
}
private void InitModulesCommon()
{
int count = this._moduleCollection.Count;
for (int i = 0; i < count; i++)
{
this._currentModuleCollectionKey = this._moduleCollection.GetKey(i);
this._moduleCollection[i].Init(this);
}
this._currentModuleCollectionKey = null;
this.InitAppLevelCulture();
}
HttpHandler
如果說HttpModule關注的是所有Inbound Request的處理的話,Handler確實關注基于某種類型的ASP.NET Resource的Request。比如一個.apsx的Web Page通過一個System.Web.UI.Page來處理。HttpHandler和他所處理的Resource通過Config中的system.web/handlers section來定義,下面是Machine.config中的定義。
<add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler" />
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory" />
<add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory" />
<add verb="*" path="*.asmx" type="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>
<add verb="*" path="*.rem" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
<add verb="*" path="*.soap" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
<add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.ascx" type="System.Web.HttpForbiddenHandler" />
<add verb="GET,HEAD" path="*.dll.config" type="System.Web.StaticFileHandler" />
<add verb="GET,HEAD" path="*.exe.config" type="System.Web.StaticFileHandler" />
<add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.cs" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.csproj" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.vb" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.vbproj" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.webinfo" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.asp" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.licx" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.resx" type="System.Web.HttpForbiddenHandler" />
<add verb="*" path="*.resources" type="System.Web.HttpForbiddenHandler" />
<add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler" />
<add verb="*" path="*" type="System.Web.HttpMethodNotAllowedHandler" />
</httpHandlers>
需要注意的是,我們不但可以單純地定義一個實現了System.Web.IHttpHandler的Type,也可以定義一個實現了System.Web.IHttpHandlerFactory 的Type。System.Web.UI.Page是一個典型的Httphandler,相信對此大家已經很熟悉了。在最后還說說另一個典型的HttpHandler:System.Web.HttpForbiddenHandler,從名稱我們不難看出,它用于那些禁止訪問的Resource,現在應該知道了為了Global.asax不同通過IIS訪問了吧。