Http Pipeline详细分析(下)
Http Pipeline详细分析(下)
文章内容
接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码:
public class Test : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
} void context_AuthenticateRequest(object sender, EventArgs e)
{
throw new NotImplementedException();
} void context_BeginRequest(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
然后添加的代码,在Pipeline里执行的时候就会把这些事件给执行了,那么如何执行并且按照什么顺序执行的呢? 在了解这些之前,我们先看看这些事件是如何在HttpApplication里暴露出来了,添加事件存放在何处的呢?阅读HttpApplication的源码,我们可以看到,所有的事件都是按照如下的形式暴露的,选择其中两个看一下:
/// <devdoc><para>[To be supplied.]</para></devdoc>
public event EventHandler BeginRequest {
add { AddSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
remove { RemoveSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
} /// <devdoc><para>[To be supplied.]</para></devdoc>
public event EventHandler AuthenticateRequest {
add { AddSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); }
remove { RemoveSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); }
}
可以发现,所有的事件都是调用AddSyncEventHookup方法添加进去的,其中第一个参数是以Event+事件名称的值,这个值是如何得来的,我们找到声明的代码:
private static readonly object EventDisposed = new object();
private static readonly object EventErrorRecorded = new object();
private static readonly object EventPreSendRequestHeaders = new object();
private static readonly object EventPreSendRequestContent = new object(); private static readonly object EventBeginRequest = new object();
private static readonly object EventAuthenticateRequest = new object();
private static readonly object EventDefaultAuthentication = new object();
private static readonly object EventPostAuthenticateRequest = new object();
private static readonly object EventAuthorizeRequest = new object();
private static readonly object EventPostAuthorizeRequest = new object();
private static readonly object EventResolveRequestCache = new object();
private static readonly object EventPostResolveRequestCache = new object();
private static readonly object EventMapRequestHandler = new object();
private static readonly object EventPostMapRequestHandler = new object();
private static readonly object EventAcquireRequestState = new object();
private static readonly object EventPostAcquireRequestState = new object();
private static readonly object EventPreRequestHandlerExecute = new object();
private static readonly object EventPostRequestHandlerExecute = new object();
private static readonly object EventReleaseRequestState = new object();
private static readonly object EventPostReleaseRequestState = new object();
private static readonly object EventUpdateRequestCache = new object();
private static readonly object EventPostUpdateRequestCache = new object();
private static readonly object EventLogRequest = new object();
private static readonly object EventPostLogRequest = new object();
private static readonly object EventEndRequest = new object();
再结合add和remove方法,可以大胆猜想,这些值应该是作为key值用的,我们先看完第2个参数,再来验证我们的猜想,第2个参数是枚举类型RequestNotification,这里我们再猜想一下,所有的事件都应该放在统一的地方,然后用这个枚举来区分。让我们先看看这个枚举类的代码:
[Flags]
public enum RequestNotification
{
BeginRequest = 1,
AuthenticateRequest = 2,
AuthorizeRequest = 4,
ResolveRequestCache = 8,
MapRequestHandler = 16,
AcquireRequestState = 32,
PreExecuteRequestHandler = 64,
ExecuteRequestHandler = 128,
ReleaseRequestState = 256,
UpdateRequestCache = 512,
LogRequest = 1024,
EndRequest = 2048,
SendResponse = 536870912,
}
发现什么了没有?虽然使用了Flags标记来记录以便进行异或查询,但是这里的枚举类型好像少了一些吧,仔细对照代码发现所有以Post开头的事件都没出现在这个枚举类里,为什么呢?那这些事件是如何声明的?回到HttpApplication类来继续查看代码,
/// <devdoc><para>[To be supplied.]</para></devdoc>
public event EventHandler PostAuthenticateRequest {
add { AddSyncEventHookup(EventPostAuthenticateRequest, value, RequestNotification.AuthenticateRequest, true); }
remove { RemoveSyncEventHookup(EventPostAuthenticateRequest, value, RequestNotification.AuthenticateRequest, true); }
}
突然发现,这个AddSyncEventHookup方法多了一个参数true,这是干什么的呢?我们去查看这个看看究竟。
internal void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification) {
AddSyncEventHookup(key, handler, notification, false);
}
private void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification, bool isPostNotification) {
ThrowIfEventBindingDisallowed(); // add the event to the delegate invocation list
// this keeps non-pipeline ASP.NET hosts working
Events.AddHandler(key, handler); // For integrated pipeline mode, add events to the IExecutionStep containers only if
// InitSpecial has completed and InitInternal has not completed.
if (IsContainerInitalizationAllowed) {
// lookup the module index and add this notification
PipelineModuleStepContainer container = GetModuleContainer(CurrentModuleCollectionKey);
//WOS 1985878: HttpModule unsubscribing an event handler causes AV in Integrated Mode
if (container != null) {
#if DBG
container.DebugModuleName = CurrentModuleCollectionKey;
#endif
SyncEventExecutionStep step = new SyncEventExecutionStep(this, (EventHandler)handler);
container.AddEvent(notification, isPostNotification, step);
}
}
}
原来这个方法有2个重新,第2个多了一个isPostNotification的布尔值参数,也就是说通过这个参数节约了很多枚举类型的声明。
我们来仔细看一下上述的代码,在刚开始的时候通过调用Events.AddHandler方法,将事件添加到Events集合里,同时这个key就是我们上面猜想的那些Event+事件名称,通过注释我们也可以知道Events是为non-pipeline来准备的,在结合if语句上面的注释,我们发现在IIS7的集成模式下这些事件是添加到另外一个地方的(通过将事件hanlder包装成SyncEventExecutionStep类型,然后调用container.AddEvent方法将事件添加到另外一个地方),也就是说if上面的Events集合是给IIS6和IIS7经典模式用的,下面的Container里的数据是给IIS7集成模式用的。
注:经典模式使用了Event+事件名称做为key值,但集成模式使用了RequestNotification枚举+ isPostNotification布尔值集合做为key值,这点区别需要注意一下。
那到底IIS7集成模式下的是存放在何处呢?通过GetModuleContainer方法,最终我们可以查到,这些事件是存放在HttpApplication的ModuleContainers属性里,这个属性的类型是PipelineModuleStepContainer[],个数就是HttpModules的个数,也就是说每个HttpModule在HttpApplication上添加的事件都放在各自的PipelineModuleStepContainer容器里。
现在我们重新回头继续来看上个章节的代码:
// Construct the execution steps array
if (HttpRuntime.UseIntegratedPipeline) {
_stepManager = new PipelineStepManager(this);
}
else {
_stepManager = new ApplicationStepManager(this);
} _stepManager.BuildSteps(_resumeStepsWaitCallback);
集成模式和经典模式(或IIS6)使用的是不同的StepManager,这个类的BuildSteps方法就是为了创建有序的ExecutionStep,其中包括各种事件的事情以及其它在各时间周期之间穿插的操作,最主要的操作,大家以前就应该知道的,比如哪个周期可以判定使用哪个HttpHandler,以及在哪个周期内执行这个HttpHandler的BeginProcessRequest方法。
由于不同的StepManager处理方式不同,我们先看IIS6以及IIS7经典模式的处理代码:
internal override void BuildSteps(WaitCallback stepCallback ) {
ArrayList steps = new ArrayList();
HttpApplication app = _application; bool urlMappingsEnabled = false;
UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;
urlMappingsEnabled = urlMappings.IsEnabled && ( urlMappings.UrlMappings.Count > 0 ); steps.Add(new ValidateRequestExecutionStep(app));
steps.Add(new ValidatePathExecutionStep(app)); if (urlMappingsEnabled)
steps.Add(new UrlMappingsExecutionStep(app)); // url mappings app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);
steps.Add(new MapHandlerExecutionStep(app)); // map handler
app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);
app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);
steps.Add(new CallHandlerExecutionStep(app)); // execute handler
app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps);
app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps);
steps.Add(new CallFilterExecutionStep(app)); // filtering
app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps);
_endRequestStepIndex = steps.Count;
app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps);
steps.Add(new NoopExecutionStep()); // the last is always there _execSteps = new IExecutionStep[steps.Count];
steps.CopyTo(_execSteps); // callback for async completion when reposting to threadpool thread
_resumeStepsWaitCallback = stepCallback;
}
看着上面的代码是不是有似曾相识的感觉,很多讲声明周期的文章都会提到20多个的事件(BeginRequest, EndRequest等),我们来看看这个方法的完整功能都是做了什么,归纳总结有5点:
- 对请求的Request进行验证,ValidateRequestExecutionStep。
- 对请求的路径进行安全检查,禁止非法路径访问(ValidatePathExecutionStep)。
- 如果设置了UrlMappings, 进行RewritePath(UrlMappingsExecutionStep)。
- 执行事件处理函数,比如将BeginRequest、AuthenticateRequest转化成可执行ExecutionStep在正式调用时候执行。
- 在这18个事件操作处理期间,根据不同的时机加了4个特殊的ExecutionStep。
- MapHandlerExecutionStep:查找匹配的HttpHandler
- CallHandlerExecutionStep:执行HttpHandler的BeginProcessRequest
- CallFilterExecutionStep:调用Response.FilterOutput方法过滤输出
- NoopExecutionStep:空操作,留着以后扩展用
需要注意的是所有的ExecuteionStep都保存在ApplicationStepManager实例下的私有字段_execSteps里,而HttpApplication的BeginProcessRequest方法最终会通过该实例的ResumeSteps方法来执行这些操作(就是我们所说的那些事件以及4个特殊的Steps)。
OK,我们继续来看IIS7集成模式下是如何处理的,上代码:
internal override void BuildSteps(WaitCallback stepCallback) {
Debug.Trace("PipelineRuntime", "BuildSteps");
//ArrayList steps = new ArrayList();
HttpApplication app = _application; // add special steps that don't currently
// correspond to a configured handler IExecutionStep materializeStep = new MaterializeHandlerExecutionStep(app); // implicit map step
app.AddEventMapping(
HttpApplication.IMPLICIT_HANDLER,
RequestNotification.MapRequestHandler,
false, materializeStep); // implicit handler routing step
IExecutionStep handlerStep = new CallHandlerExecutionStep(app); app.AddEventMapping(
HttpApplication.IMPLICIT_HANDLER,
RequestNotification.ExecuteRequestHandler,
false, handlerStep); // add implicit request filtering step
IExecutionStep filterStep = new CallFilterExecutionStep(app); // normally, this executes during UpdateRequestCache as a high priority module
app.AddEventMapping(
HttpApplication.IMPLICIT_FILTER_MODULE,
RequestNotification.UpdateRequestCache,
false, filterStep); // for error conditions, this executes during LogRequest as a high priority module
app.AddEventMapping(
HttpApplication.IMPLICIT_FILTER_MODULE,
RequestNotification.LogRequest,
false, filterStep); _resumeStepsWaitCallback = stepCallback;
}
以上代码有2个地方和IIS6不相同:
- IIS7集成模式没有使用MapHandlerExecutionStep来装载ExecutionStep(也就是查找对应的HttpHandler),而是通过MaterializeHandlerExecutionStep类来获得HttpHandler,方式不一样,但最终都是调用HttpApplication.GetFactory方法来获取的,只不过IIS7集成模式有一些特殊操作而已罢了。
- IIS7集成模式是通过HttpApplication的AddEventMapping方法来添加事件的,从而将事件再次加入到前面所说的ModuleContainers容器。
另外有个很有技巧的代码:上述4个Steps所加的周期都不是准确的周期,比如CallHandlerExecutionStep应该是加载RequestNotification的枚举值PreExecuteRequestHandler 和ExecuteRequestHandler之间,为什么呢?因为本身CallHandlerExecutionStep只是一个特殊的step而不暴露事件的,所以枚举里也没有,那怎么办?回头看看AddEventMapping方法的第一个参数,它代表的是HttpModule的名字,查看其中的代码得知,在执行所有事件的时候,会遍历所有HttpModuel名称集合然后先执行全部BeginRequest事件,再全部执行AuthenticateRequest事件,以此类推,那我们能不能来伪造一个HttpModule的名称作为参数传递给AddEventMapping方法呢,答案是肯定的,看上面的代码,发现有2个伪造的名称分别是常量字符串HttpApplication.IMPLICIT_FILTER_MODULE(值为AspNetFilterModule)和HttpApplication.IMPLICIT_HANDLER(值为ManagedPipelineHandler),而因为之前其它HttpModule里的各种事件都已经load完了,所以这2个伪造HttpModule的是放在集合的最后面,所以在执行ExecuteRequestHandler类别的事件的时候,最后一个事件肯定就是这个伪造HttpModule的事件,再加上伪造HttpModule里没有别的事件,所以它对应的ExecutionStep的执行效果其实和IIS6里CallHandlerExecutionStep的效果是一样的,就这样,通过一个很奇特的技巧达到同样的目的。
最后,我们来总结一下,在IIS6和IIS7经典模式下,是用 Event+事件名称做key将所有事件的保存在HttpApplication的Events属性对象里,然后在BuildSteps里统一按照顺序组装,中间加载4个特殊的ExecutionStep,最后在统一执行;在IIS7集成模式下,是通过HttpModule名称+RequestNotification枚举值作为key将所有的事件保存在HttpApplication的ModuleContainers属性对象里,然后也在BuildSteps里通过伪造HttpModule名称加载那4个特殊的ExecutionStep,最后按照枚举类型的顺序,遍历所有的HttpModule按顺序来执行这些事件。读者可以自行编写一个自定义的HttpModuel来执行这些事件看看效果如何。
最后关于Pipeline完整的图如下:
最后,需要注意的是:HttpApplication不是HttpRuntime所创建,HttpRuntime只是向HttpApplicationFactory提出请求,要求返回一个HttpApplication对象。 HttpApplicationFactory在接收到请求后,会先检查是否有已经存在并空闲的对象,如果有就取出一个HttpApplication对象返回给HttpRuntime,如果没有的话,则要创建一个HttpApplication对象给HttpRunTime。
参考资料:
http://msdn.microsoft.com/en-us/library/bb470252.aspx
http://www.cnblogs.com/zhaoyang/archive/2011/11/16/2251200.html
http://www.brainbell.com/tutorials/ASP/The_ASP.NET_Pipeline.html
http://learn.iis.net/page.aspx/243/aspnet-integration-with-iis/
同步与推荐
本文已同步至目录索引:MVC之前的那点事儿系列
MVC之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。
Http Pipeline详细分析(下)的更多相关文章
- MVC之前的那点事儿系列(5):Http Pipeline详细分析(下)
文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class ...
- MVC之前的那点事儿系列(4):Http Pipeline详细分析(上)
文章内容 继续上一章节的内容,通过HttpApplicationFactory的GetApplicationInstance静态方法获取实例,然后执行该实例的BeginProcessRequest方法 ...
- scrapy爬虫具体案例步骤详细分析
scrapy爬虫具体案例详细分析 scrapy,它是一个整合了的爬虫框架, 有着非常健全的管理系统. 而且它也是分布式爬虫, 它的管理体系非常复杂. 但是特别高效.用途广泛,主要用于数据挖掘.检测以及 ...
- scrapy爬虫具体案例详细分析
scrapy爬虫具体案例详细分析 scrapy,它是一个整合了的爬虫框架, 有着非常健全的管理系统. 而且它也是分布式爬虫, 它的管理体系非常复杂. 但是特别高效.用途广泛,主要用于数据挖掘.检测以及 ...
- 13万字详细分析JDK中Stream的实现原理
前提 Stream是JDK1.8中首次引入的,距今已经过去了接近8年时间(JDK1.8正式版是2013年底发布的).Stream的引入一方面极大地简化了某些开发场景,另一方面也可能降低了编码的可读性( ...
- ZIP压缩算法详细分析及解压实例解释
最近自己实现了一个ZIP压缩数据的解压程序,觉得有必要把ZIP压缩格式进行一下详细总结,数据压缩是一门通信原理和计算机科学都会涉及到的学科,在通信原理中,一般称为信源编码,在计算机科学里,一般称为数据 ...
- LinkedList详细分析
一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据remove()7.数据获取get()8.数据复制clo ...
- Android-Native-Server 启动和注册详细分析
Android-Native-Server 启动和注册详细分析 以mediaService为实例来讲解: mediaService的启动入口 是一个 传统的 main()函数 源码位置E:\ ...
- px,dp,dip,sp,in,mm,pt详细分析
px,dp,dip,sp,in,mm,pt详细分析 px :(pixels),屏幕的像素点,不同的设备显示效果相同,一般我们HVGA代表320x480像素,这个用的比较多. dip :(devi ...
随机推荐
- 使用批处理给IIS添加MIME类型
原文 使用批处理给IIS添加MIME类型 @echo off set /p warn="警告:本脚本会清空全部站点原有MIME类型,输入y按回车继续,直接回车退出:" if & ...
- (大数据工程师学习路径)第一步 Linux 基础入门----用户及文件权限管理
用户及文件权限管理 实验介绍 1.Linux 中创建.删除用户,及用户组等操作. 2.Linux 中的文件权限设置. 一.Linux 用户管理 Linux 是一个可以实现多用户登陆的操作系统,比如“李 ...
- [TroubleShooting] The remote copy of database xx has not been rolled forward to a point in time
Steps: 1. backup database TestMirror on Pricipal server 2. backup database log of TestMirror on Pric ...
- 单点登录SSO原则的实现
单点登录SSO(Single Sign On)简单地把它是下一个在多系统环境中共存,在用户登录,不要在其他系统上的登录.这是用户第一次登录得到所有其他系统的信任. 单点登录在一个大型网站非常频繁使用, ...
- JavaScript模块化编程之require.js与sea.js
为什么要模块化:当今,网站以不再是一个简单的页面,JavaScript也不再是做一些简单的脚本验证,随着WEB.20时代到来,前端工程师面临的必将是越来越庞大的JavaScript代码,越来越复杂的内 ...
- MessageDigest简要
本文博客原 參考文章:http://blog.sina.com.cn/s/blog_4f36423201000c1e.html 一.概述 java.security.MessageDigest类用于为 ...
- C#关于HttpClient的应用(二):融云IM集成
public class RcHttpClient:BaseHttpClient { private String appKey; private String appSecret; public R ...
- oracle_分区表的新增、修改、删除、合并。普通表转分区表方法
一. 分区表理论知识Oracle提供了分区技术以支持VLDB(Very Large DataBase).分区表通过对分区列的判断,把分区列不同的记录,放到不同的分区中.分区完全对应用透明. Oracl ...
- 【C++基础】类的组合
所谓类的组合是指:类中的成员数据是还有一个类的对象或者是还有一个类的指针或引用.通过类的组合能够在已有的抽象的基础上实现更复杂的抽象. 比如: 1.按值组合 #include<iostream. ...
- Entity Framework加载相关实体——延迟加载Lazy Loading、贪婪加载Eager Loading、显示加载Explicit Loading
Entity Framework提供了三种加载相关实体的方法:Lazy Loading,Eager Loading和Explicit Loading.首先我们先来看一下MSDN对三种加载实体方法的定义 ...