【转】ASP.NET服务器对于请求的处理过程
【http://www.cnblogs.com/DebugLZQ/archive/2011/12/26/2302319.html】
前面DebugLZQ介绍了一个最简单的Web服务器的,显然刚刚完成的Web服务器实在是太原始了,如果希望创建一个功能更加强大的ASP.NET服务器,Cassini是一个不错的起点。Cassini是MS发布的一个简单的ASP.NET服务器,也是Visual Studio 中ASP.NET Development Server的前身,它不需要IIS的支持,也是通过Socket来处理网络连接。Cassini提供了所有的源代码,DebugLZQ在Codeplex找到的最新版的下载链接如下:http://cassinidev.codeplex.com/。dmitryr在他的博客中提供了Cassini的维护信息,下面是他的博客地址:http://blogs.msdn.com/b/dmitryr/archive/2008/10/03/cassini-for-framework-3-5.aspx,这里面也可以下载。
进入正题。。。
第一步①:
我们假设,现在请求已经到达了ASP.NET服务器,为了处理请求,ASP.NET 在服务器上创建了 HttpRequest 类型的对象以表示请求参数,HttpResponse 类型的对象以表示回应的处理对象。另外,还有 HttpServerUtility 类型的对象处理网站虚拟路径与服务器文件系统路径之间的映射关系。也就是说,为了处理一次 HTTP 请求,我们需要众多的对象来表示处理请求所需要的数据,处理请求的方法就需要接收诸多的参数来完成处理工作,为了进一步简化服务器端对参数的处理工作,ASP.NET 中定义了 HttpContext 类型来统一处理参数的表示问题。
针对每一次请求,ASP.NET 将创建一个处理这次请求所使用的 HttpContext 对象实例,这个对象实例将用来在 ASP.NET 服务器的处理过程中传递所有需要的参数,在请求到达 ASP.NET 服务器之后,这个对象将被创建出来,在一次请求处理之后,这个对象将被丢弃掉。由于在大多数的处理环节都需要用到这个对象,因此,HttpContext 类中还提供了一个名为 Current 的静态属性,以便于在处理过程中,不需要通过方法的参数而直接取得当前的上下文对象。当然,在没有请求的情况下,通过这个属性获得的结果将是 null。HttpContext中得常用属性、方法等详细介绍请参见MSDN:http://msdn.microsoft.com/zh-cn/library/system.web.httpcontext.aspx
虽然使用 HttpContext 所提供方法的机会并不多,但是,HttpContext 提供了一些底层的方法,在编写 HttpModule 的时候非常有用,例如:RewritePath 方法用于将 HttpRequest 中的原始请求地址替换为指定的地址,以便在后面的处理中,使 ASP.NET 认为请求的实际地址是这个新的地址。这个方法在使用无 Cookie 的会话中非常有用,使用它将嵌入在 URL 中的 SessionID 取出来,将 URL 地址修正为一个普通的 URL,这样,后继的处理就不会受到影响。另外,在 URL重写中,这个方法也很有用,可以将一个看似静态地址的 URL 映射到一个普通的 ASP.NET 处理上。
第二步②:
当 HttpContext 对象创建之后,HttpRuntime 将随后创建一个用于处理请求的对象,这个对象的类型为 HttpApplication。
(补充:System.Web.HttpRuntime类是整个ASP.NET服务器处理的入口。每个Web应用程序域中存在一个System.Web.HttpRuntime类,这个类提供了一系列的静态属性,反映了Web应用程序域的设置信息。)
在 ASP.NET 内部,HttpRuntime 管理一个定义在 System.Web 命名空间下的 HttpApplicationFactory 类的实例,HttpApplicationFactory 通过工厂模式管理 HttpApplication 对象,在 HttpApplicationFactory 内部维护了一个 HttpApplication 对象池,使得被创建的 HttpApplication 对象可以被重复使用。但是,每一个 HttpApplication 对象每一次仅仅用于处理一个请求,这样,对于 ASP.NET 程序员来说,也就不需要考虑 HttpApplication 中多个请求并发的处理问题了。
在实际的请求处理过程中,我们还需要进行大量的工作,例如,检查当前的请求是由哪一个用户发起的,以此我们就可以针对不同的用户进行不同的处理;或者根据用户来决定是否对用户的请求进行处理,对于没有权限的用户返回一个缺少相应权限的回应等。如果我们在一个方法中来完成这些任务,显然会造成方法的过度臃肿。在 HttpApplication 中,利用 .NET 中的事件机制,通过在处理过程中依次发出的多个事件,将这个处理过程分解为多个步骤,这个处理机制通常我们称为处理管道。下面我们将会详细讨论处理管道的内在机制和 HttpApplication 的多个事件。
·HttpApplication处理管道
所谓的处理管道,就是处理复杂问题的时候,将处理的过程分解为多个处理步骤,我们将这种经过多个步骤的处理方式称为处理管道。在 .NET 中,借助于事件的强大威力,我们可以通过处理管道将复杂的处理步骤封装起来,通过事件将处理过程的多个步骤暴露给程序员,以便于程序员对管理管道进行扩展。下图概述了HttpApplication 处理管道的工作过程。
于一个管道来说,它往往要暴露出大量的事件,通过这些事件,提供程序员的扩展机制。
但是,对于一个有着众多事件的类来说,定义大量的事件意味着创建对象的时候需要付出创建事件的成本,因为在 .NET 中,所谓的事件就是一个受限制的委托成员,定义多个事件,意味着在创建的对象中将会需要更多的存储空间。针对这个问题,在 System.ComponentModel.Component 类中,提供了处理多个事件的基础:Events 属性,它的类型为 System.ComponentModel. EventHandlerList,这是一个线性的字典,当需要事件的时候,就通过 key 将事件保存到集合中,没有对应的事件,就不会付出创建事件的成本,这样,通过 EventHandlerList 可以在一个集合中管理多个事件对象,节省了事件对象占用的空间。
HttpApplication对象是ASP.NET中处理请求的重要对象,但是,这种类型的对象实例不是由程序员来创建的,而是由ASP.NET帮助我们创建的。为了便于扩展处理工作,HttpApplication采用处理管道的方法进行处理,将处理的过程分为多个步骤,每个步骤通过事件的形式暴露给程序域,这些事件按照固定的处理顺序依次触发,程序员通过编写事件处理方法就可以自定义每一个请求的扩展处理过程。
对于HttpApplication来说,到ASP.NET 4.0版本,提供了19个标准事件,引发事件的顺序如下图:
可参见MSDN:http://msdn.microsoft.com/zh-cn/library/system.web.httpapplication.aspx
·处理过程的简单介绍:
在 ASP.NET 中,ASP.NET服务器对于每一次请求的处理过程是相同的,都要经过这个 HttpApplication 的处理管道。管道内部的处理过程是固定的,在服务器处理请求的各个阶段,伴随着处理的进行,依次触发对应的事件,以便于程序员在处理的各个阶段完成自定义的处理工作。
首先触发的事件是 BeginRequest,这个事件标志着 ASP.NET 服务器处理工作的开始,也是程序员在 ASP.NET 中针对请求所能够处理的第一个事件。
开始处理请求后,第一个重要的工作就是确定请求用户的身份以实现安全机制,这个工作通过AuthenticateRequest 和 PostAuthenticateRequest 两个事件提供检查当前请求用户身份的机会。显然,AuthenticateRequest 表示开始检查用户的身份,而 PostAuthenticateRequest 则表示用户身份已经检查完成,检查后的用户可以通过 HttpContext 的 User 属性获取到。这个属性的类型为 System.Security.Principal.IPrincipal,IPrincipal又有一个名为 Identity,类型为 System.Security.Principal.IIdentity的属性,IIdentity 有一个类型为 bool 型的属性 IsAuthenticated ,表示当前请求的用户是否已经被验证,或者说确定了用户是否是匿名用户,IsAuthenticated 如果为 false,那么,表示这是一个匿名用户,如果为 true,那么通过IIdentity 类型为 string 的 Name 属性,这就是当前请求的用户名。
当 ASP.NET 获取用户的身份之后,根据当前请求的用户身份,开始请求权限的检查工作。当第 4 个事件AuthorizeRequest触发的时候,表示开始进行用户的权限检查,而第 5 个事件 PostAuthorizeRequest则标志着已经完成了用户权限的检查工作。如果用户没有通过安全检查,一般情况下,将跳过剩下的事件,直接触发 EndRequest 事件结束请求的处理过程。
当用户获取了请求的权限,那么服务器开始准备用最快的方式来使用户得到回应的结果。ResolveRequestCache 事件标志着到从前缓存的结果中进行检查,看看是否可以直接从以前缓存的结果中直接取得处理的结果, PostResolveRequestCache 表示缓存检查的结束。
当不能从缓存中获取结果的时候,必须通过一次处理来计算出当前请求的结果。在 ASP.NET 中,用于处理请求以得到结果的对象称为处理程序 Handler,在 ASP.NET 中提供了许多的处理程序,程序员也可以自定义处理程序。为了处理这个请求, ASP.NET 必须按照匹配规则找到一个处理当前请求的处理程序,PostMapRequestHandler 事件表示 ASP.NET 已经获取了这个处理程序,HttpContext 的 Handler 属性就表示这个处理程序对象。从上面的分析可以看到,HttpContext 的 Handler 属性到这里才有实际的意义。
得到了处理程序之后,还不能马上开始进行处理,这是由于处理请求还需要许多与这个请求有关的数据,比如说,这个用户在上一次向服务器发请求的时候,在服务器上保存了一些这个用户特有的数据。从 ASP 时代开始,Session 这个概念就出现在 Web 开发中,提供基于会话的状态管理,由于 HTTP 协议的无状态性,状态管理的问题是 Web 开发的一个核心问题。
为了获取这个用户在以前保存的专属数据, AcquireRequestState 事件给程序员提供了一个切入点,PostAcquireRequestState 事件则表示已经完成了用户数据的获取工作,可以在处理中使用了。
万事俱备,只欠东风,PreRequestHandlerExecute 事件用来通知程序员,处理程序就要开始进行处理工作了,如果在用户的状态已经获取之后,还有需要在处理程序处理之前进行的工作,那么,就在这个事件中处理吧。
在 PreRequestHandlerExecute 事件之后,ASP.NET 服务器将通过执行处理程序完成请求的处理工作。这个处理程序可能是一个 Web 窗体,也可能是一个 Web 服务。这个工作在第 11 个事件和第 12 个事件之间完成。
处理程序完成之后,服务器开始进行扫尾工作,PostRequestHandlerExecute 事件通知程序员,ASP.NET 服务器的处理程序已经完成。
在处理完成之后,由于在处理程序中,用户可能修改了用户特定的专属数据,那么,修改之后的用户状态数据可能需要进行序列化或者进行保存处理。ReleaseRequestState 事件通知程序员需要释放这些状态数据,PostReleaseRequestState 则表示已经释放完成。
在处理完成之后,如果希望将这次处理的结果缓存起来,以便于在后继的请求中可以直接使用这个结果,UpdateRequestCache 事件提供了处理的机会,PostUpdateRequestCache则表示缓存已经更新完成。
在 ASP.NET 4.0 中,新增加了两个事件完成处理的日志工作:LogRequest 表示将这次请求记入日志中,PostLogRequest 表示完成了日志工作。
在前述的事件中,请求不一定要经过所有的事件,比如说,用户没有经过授权的检查,那么将跳过后面的事件,但是,EndRequest 事件是所有请求都要经过的最后一个 HttpApplication处理管道的事件,也是程序员处理的 ASP.NET 处理请求中的最后一个机会。这个事件之后,处理的结果将被回应到浏览器,完成 ASP.NET 服务器的处理工作。