转自:http://www.cnblogs.com/artech/archive/2007/09/13/891262.html
相信大家都使用过 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 方法,进而从非托管的环境进入了托管的环境。
[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 的实现:
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 的对象:
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 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); } }
对象上面的代码没有必要深究,我们只需要了解大体的执行流程就可以了,下面这一段伪代码基本上体现整个执行过程:
HttpContext context = new HttpContext(wr, false); 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中。
2. 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,并按照触发的时间先后顺序排列:
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并存的现象。
3. 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很简单,仅仅有两个成员:
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)] 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 。
<httpModules> <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方法。下面是其实现:
private void InitModules() { 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中的定义。
<httpHandlers> <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访问了吧。