ASP.NET管道处理模型
原理说明
- Http请求到达服务器,如果是首次Http请求,则会初始化站点(加载路由规则、注册区域、添加过滤器等);
- http.sys监听到Http请求,交给aspnet_ISAPI(实现了ISAPI接口的程序),aspnet_ISAPI的isapiRuntime将请求打包成IsapiWorkRequest对象,以下简称wr并将wr对象并放入指定的某个队列,队列与IIS应用程序池对应,http.sys通知w3svc处理队列里的http请求;
- .net开始接管请求了,由HttpRunTime接管,从队列取出wr对象,将wr封装成
HttpContext(httpContext = new HttpContext(wr, false)
来自HttpRunTime源码,如果HttpContext实例化异常,会wr.EndOfRequest()
返回400错误Bad Request,开始进入ASP.NET管道; - HttpApplicationFactory这个工厂将HttpContext作为参数,从对象池里创建或获取HttpApplication对象(管道里重要对象之一)
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext);
来自HttpRunTime源码,这个对象实现了IHttpHandler
接口(一般处理程序就是实现了这个接口),可以自定义这个HttpApplicationFactory._customApplication;
,如果没有自定义的,则返回内置的HttpApplication对象之前,如果是首次请求前会初始化,比如执行Global.asax里的Application_Start()
方法; - HttpApplication对象初始化时根据配置文件初始化多个HttpModule(管道里重要对象之一),也可以自定义HttpModule为HttpApplication注册事件,HttpApplication有25个事件,依次执行22个事件;
- 执行到PostResolveRequestCache事件时,会匹配MVC路由,如果是请求已存在的物理文件,则直接返回;如果匹配到MVC路由则进入MVCHttpHandler,MVCHttpHandler则会通过控制器工厂得到控制器实例、再反射执行MVC的3种Filter、再执行Action,如果未能匹配到路由,则按配置到指定HttpHandler,则进入指定的HttpHandler,如果还是匹配不到HttpHandler,则返回404;
- 也可以自定义HttpHandle,用于处理特殊类型的Http请求;
- ASP.NET Framework处理一个Http Request的流程:
HttpRequest–>http.sys–>w3wp.exe–>ASPNET_ISAPI.dll–>AppDomainFactory–>IsapiRuntime–>IsapiWorkerRequest–>HttpRuntime–>HttpContext–>HttpApplicationFactory–>HttpApplication–>HttpModule–>HttpHandlerFactory–>HttpHandler–>HttpHandler.ProcessRequest()–>HttpModule–>http.sys - ASP.NET 请求处理过程是基于管道模型的,这个管道模型是由多个HttpModule(HTTP模块)和最多一个HttpHandler组成,ASP.NET把http请求依次传递给管道中各个HttpModule,最终被HttpHandler处理,处理完成后,再次经过管道中的HttpModule,把结果返回给客户端。我们可以在每个HttpModule中都可以干预请求的处理过程。
- 在http请求的处理过程中,只能调用一个HttpHandler,但可以调用多个HttpModule。
- HttpHandler处理完请求后,修改队列里对象的状态,IIS将修改状态了的对象通过Socket返回给客户端;
- 这个是从网上抄的:这里以IIS6.0为例,它在工作进程w3wp.exe中会利用aspnet_isapi.dll加载.NET运行时。IIS6.0引入了应用程序池的概念,一个工作进程对应着一个应用程序池。一个应用程序池可以承载一个或多个Web应用。如果HTTP.SYS(HTTP监听器,是Windows TCP/IP网络子程序的一部分,用于持续监听HTTP请求)接收的请求是对该Web应用的第一次访问,在成功加载运行时后,IIS会通过AppDomainFactory为该Web应用创建一个应用程序域。也就是说一个应用程序池中会有多个应用程序域,它们共享一个工作进程资源,但是又不会互相牵连影响。随后一个特殊的运行时IsapiRuntime被加载,会接管该HTTP请求。IsapiRuntime首先会创建一个IsapiWorkerRequest对象来封装当前的HTTP请求,随后将此对象传递给ASP.NET运行时HttpRunTime。从此时起,HTTP请求正式进入了ASP.NET管道。HttpRunTime会根据IsapiWorkerRequest对象创建用于表示当前HTTP请求的上下文对象HttpContext。随着HttpContext对象的创建,HttpRunTime会利用HttpApplicationFactory创建或获取现有的HttpApplication对象。HttpApplication负责处理当前的HTTP请求。在HttpApplication初始化过程中,ASP.NET会根据配置文件加载并初始化注册的HttpModule对象。对于HttpApplication来说,在它处理HTTP请求的不同阶段会触发不同的事件,而HttpModule的意义在于通过注册HttpApplication的相应事件,将所需的操作注入整个HTTP请求的处理流程。最终完成对HTTP请求的处理在HttpHandler中,不同的资源类型对应着不同类型的HttpHandler。
- http.sys:HTTP协议栈,HTTP Protocol Stack, HTTP监听器,是Windows TCP/IP网络子程序的一部分,用于持续监听HTTP请求
HttpApplication
- HttpApplication是整个ASP.NET基础架构的核心,它负责处理分发给它的HTTP请求。
- HttpApplication对象里有公开25个事件,以便注入方法,还有HttpContext属性;
- global.asax文件为每个Web应用程序提供了一个从HttpApplication派生的Global类。该类包含事件处理程序,如Application_Start。
- 每个Web应用程序都会有一个Global实例,作为应用程序的唯一入口,应用程序中只有一个Global对象实例,但是可不是只有一个HttpApplication对象实例。
- ASP.NET运行时维护一个HttpApplication对象池;
- 按照实现的先后顺序列出了HttpApplication在处理每一个请求时触发的事件名称:
名称 | 描述 |
---|---|
BeginRequest | HTTP管道开始处理请求时,会触发BeginRequest事件 |
AuthenticateRequest,PostAuthenticateRequest | ASP.NET先后触发这两个事件,使安全模块对请求进行身份验证 |
AuthorizeRequest,PostAuthorizeRequest | ASP.NET先后触发这两个事件,使安全模块对请求进程授权 |
ResolveRequestCache,PostResolveRequestCache | ASP.NET先后触发这两个事件,以使缓存模块直接对请求直接进行响应(缓存模块可以将响应内容进程缓存,对于后续的请求,直接将缓存的内容返回,从而提高响应能力)。 |
PostMapRequestHandler | 对于访问不同的资源类型,ASP.NET具有不同的HttpHandler对其进程处理。对于每个请求,ASP.NET会通过扩展名选择匹配相应的HttpHandler类型,成功匹配后,该实现被触发 |
AcquireRequestState,PostAcquireRequestState | ASP.NET先后触发这两个事件,使状态管理模块获取基于当前请求相应的状态,比如SessionState |
PreRequestHandlerExecute,PostRequestHandlerExecute | ASP.NET最终通过一请求资源类型相对应的HttpHandler实现对请求的处理,在实行HttpHandler前后,这两个实现被先后触发 |
ReleaseRequestState,PostReleaseRequestState | ASP.NET先后触发这两个事件,使状态管理模块释放基于当前请求相应的状态 |
UpdateRequestCache,PostUpdateRequestCache | ASP.NET先后触发这两个事件,以使缓存模块将HttpHandler处理请求得到的相应保存到输出缓存中 |
LogRequest,PostLogRequest | ASP.NET先后触发这两个事件为当前请求进程日志记录 |
EndRequest | 整个请求处理完成后,EndRequest事件被触发 |
- 对于一个ASP.NET应用来说,HttpApplication派生于global.asax文件,我们可以通过创建global.asax文件对HttpApplication的请求处理行为进行定制。global.asax采用一种很直接的方式实现了这样的功能,这种方式既不是我们常用的方法重写(Method Overriding)或者事件注册,而是直接采用方法名匹配。在global.asax中,我们按照这样的方法命名规则进行事件注册:Application_{Event Name}。比如Application_BeginRequest方法用于处理HttpApplication的BeginRequest事件。
- HttpApplication扩展有2种办法(对http请求处理,新增功能):
- 自定义HttpModule,下面会说到;
- 在Global里新增方法,方法命名规则:HttpModule名称_事件名称,其中事件名称的事件可以在HttpModule里新增,或沿用asp.net默认加载的HttpModule,比如Seesion_start(),一般用于ASP.NET默认HttpModule进行扩展;
HttpModule
- 在HttpApplication初始化过程中,ASP.NET会根据配置文件(ASP.NET默认配置+项目自己的web.config)加载并初始化注册的HttpModule对象,注册的HttpModule对象初始化后,存放在了HttpApplication的Modules属性之中
- ASP.NET默认配置其路径位于:C:WindowsMicrosoft.NETFramework64v4.0.30319Configweb.config的httpModules节点里,当然也可以删除系统默认的HttpModule,即在项目的web.config文件HttpModules节点里,新增
- HttpModule是针对所有请求的,且Modules集合中的所有HttpModule都要依次执行请求处理;
- 自定义一个HttpModule,首先新增一个比如名称为MyHttpModule的类并实现IhttpModule接口的类,以便对ASP.NET管道进行处理,比如:session鉴权、黑白名称、日志、URL改写等;
- 实现接口的方法说明:
- Dispose方法:这个方法给予HTTP模块在对象被垃圾收集之前执行清理的机会。此方法一般无需编写代码。
- Init()方法:HttpApplication对象一共25个管道事件,给需要的事件注册方法;
- BeginRequest 当ASP.NET运行时接收到新的HTTP请求的时候引发这个事件。
- AuthenticateRequest 当ASP.NET 运行时准备验证用户身份的时候引发这个事件。
- AuthorizeRequest 当ASP.NET运行时准备授权用户访问资源的时候引发这个事件。
- IIS7集成模式中,配置文件web.config里system.webServer节点下modules新增一行 ,完成以上2步自定义HttpModule完成;
- HttpModule生命周期示意图:
事件的触发顺序:
HttpHandler
- 与HttpModule针对所有的请求文件不同,HttpHandler是针对某一类型的文件(比如图片防盗链,jpg、gif等后缀的配置为指定的HttpHandler里判断referer,或者Word文档类配置为指定HttpHandler处理,不给下载),如果http上下文(httpContext)是请求存在的物理文件:IsRouteToExistingFile(httpContext),则不会走MVC的路由,否则可能要设置MVC的忽略路由;
- HttpHandler是HTTP请求的处理中心,真正地对客户端请求的服务器页面做出编译和执行,并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中,HttpHandler与HttpModule不同,一旦定义了自己的HttpHandler类,那么它对系统的HttpHandler的关系将是“覆盖”关系,但也有可能被HttpModule拦截掉,请求到不了HttpHandler;
- 和HttpModule一样,HttpHandler对象的建立与请求路径模式之间的映射关系,也需要通过配置文件,如果这种类型的请求,匹配到了MVC的路由,则要新增一个忽略路由设置,忽略MVC路由,用web.config里的path匹配;
- 在C:WindowsMicrosoft.NETFramework64v4.0.30319Configwebconfig文件中,也可以找到ASP.NET内置的HttpHandler配置。
- 自定义一个HttpHandle,新增一个类,名称以Handle结尾,比如ZZHandler.cs,实现IHttpHandler接口,一般处理程序.ashx,就是实现了IHttpHandler接口的类;
- 实现接口方法/属性的说明:
- IsReusable:返回false;
- ProcessRequest():处理逻辑,最终调用context.Response.Write(),返回内容;
- 修改配置文件web.config:
Web.Config配置文件
<httpHandlers>
<add name="ZZ" verb="*" path="*" type="类全名,DLL文件名"></add>
</httpHandlers>
<!--
Verb属性:指定了处理程序支持的HTTP动作。*-支持所有的HTTP动作;“GET”-支持Get操作;“POST”-支持Post操作;“GET, POST”-支持两种操作。
Path属性:指定了需要调用处理程序的路径和文件名(可以包含通配符)。“*”、“*.aspx”、“showImage.aspx”、“test1.aspx,test2.aspx”,如果匹配到MVC的路由,则要新增一个忽略路由设置
Type属性:类全名,DLL文件名-->
MVC管道区别
- MVC与一般处理程序、ASPX等的管道基本是一致的,只是MVC管道新增了一个UrlRoutingModule(HttpModule),并在Init方法里,为HttpApplication对象的PostResolveRequestCache事件注册了动作,注意,是在MapRequestHeadler事件前,MapRequestHeadler事件是读取web.config里的Handlers节点的文件后缀,以便匹配Handler;
- 用http上下文(httpContext)匹配MVC路由,得到路由匹配结果,如果请求的是已存在的文件,则返回null,所以请求已存在的文件,不会匹配MVC的路由规则,UrlRoutingModule扩展无效;
- 然后用路由结果(不为空,匹配到了路由)获取一个IRouteHandler对象;
- IRouteHandler对象,获取一个HttpHandler,即一个MvcHandler,所以MVC也最终由MvcHandler处理的;
- 用http上下文(httpContext)匹配MVC路由,得到路由匹配结果,如果请求的是已存在的文件,则返回null,所以请求已存在的文件,不会匹配MVC的路由规则,UrlRoutingModule扩展无效;
- MvcHandler源码里的ProcessRequest()方法,会生成对应控制器对象,控制器对象再调用方法controller.Execute(this.RequestContext),再调用ExecuteCore得到Action名称,最后执行Filter(权限验证、AcitonFilter等)以及方法本身;
返回结果
- ActionResult是抽象类,里面有个抽象方法:ExecuteResult(),比如JsonResult类,就是设置一下ContentType="application/json"以及将数据序列化写入Response里去,所以也可以自定义一个ActionResult,比如XMLResult实现处理结果用XML返回,只要重写ExecuteResult方法即可;
- 在控制器实例化后,调用的是:ControllerActionInvoker–InvokeAction—查询Filter并执行–OnActionExecuting–Action执行–OnActionExecuted—OnResultExecuting–ActionResult.ExecuteResult生成返回结果到Respose—OnResultExecuted
- 如果不是返回View(),则直接将数据写入Respose里去;
- 如果返回的是View(),则其中ExecuteResult方法执行大致步骤:FindView–Render–把cshtml生成的类.Execute()方法生成html拼接好并写入response,cshtml其实是System.Web.Mvc.WebViewPage派生的子类;