MVC之前的那点事儿系列(5):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点:

  1. 对请求的Request进行验证,ValidateRequestExecutionStep。

  2. 对请求的路径进行安全检查,禁止非法路径访问(ValidatePathExecutionStep)。 

  3. 如果设置了UrlMappings, 进行RewritePath(UrlMappingsExecutionStep)。

  4. 执行事件处理函数,比如将BeginRequest、AuthenticateRequest转化成可执行ExecutionStep在正式调用时候执行。

  5. 在这18个事件操作处理期间,根据不同的时机加了4个特殊的ExecutionStep。
    1. MapHandlerExecutionStep:查找匹配的HttpHandler

    2. CallHandlerExecutionStep:执行HttpHandler的BeginProcessRequest

    3. CallFilterExecutionStep:调用Response.FilterOutput方法过滤输出

    4. 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不相同:

  1. IIS7集成模式没有使用MapHandlerExecutionStep来装载ExecutionStep(也就是查找对应的HttpHandler),而是通过MaterializeHandlerExecutionStep类来获得HttpHandler,方式不一样,但最终都是调用HttpApplication.GetFactory方法来获取的,只不过IIS7集成模式有一些特殊操作而已罢了。

  2. 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之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

时间: 2024-08-09 23:56:16

MVC之前的那点事儿系列(5):Http Pipeline详细分析(下)的相关文章

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下)(转载)

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下) 文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class Test : IHttpModule { public void Init(HttpApplication context) { context.BeginRequest += new EventHandler(context_

MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)(转载)

MVC之前的那点事儿系列(3):HttpRuntime详解分析(下) 文章内容 话说,经过各种各样复杂的我们不知道的内部处理,非托管代码正式开始调用ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime继承了IISPAIRuntime接口,该接口可以和COM进行交互,并且暴露了ProcessRequest接口方法).至于为什么要调用这个方法,大叔也不太清楚,找不到微软相关的资料哦.但大叔确定该方法就是我们进入HttpRuntime的正式大门,接着看吧. publi

MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的?(转载)

MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的? 文章内容 上个章节我们讲到了,可以在HttpModules初始化之前动态添加Route的方式来自定义自己的HttpHandler,最终接管请求的,那MVC是这么实现的么?本章节我们就来分析一下相关的MVC源码来验证一下我们的这个问题. 先创建一个MVC3的Web Application,选择默认的模板以便创建以后就默认包含HomeController和AccountController.我们知道MVC要先接管请求才能通过

MVC之前的那点事儿系列(8):UrlRouting的理解(转载)

MVC之前的那点事儿系列(8):UrlRouting的理解 文章内容 根据对Http Runtime和Http Pipeline的分析,我们知道一个ASP.NET应用程序可以有多个HttpModuel,但是只能有一个HttpHandler,并且通过这个HttpHandler的BeginProcessRequest(或ProcessRequest)来处理并返回请求,前面的章节将到了再MapHttpHandler这个周期将会根据请求的URL来查询对应的HttpHandler,那么它是如何查找的呢?

MVC之前的那点事儿系列(4):HttpPipeline详细分析(上)(转载)

MVC之前的那点事儿系列(4):HttpPipeline详细分析(上) 文章内容 继续上一章节的内容,通过HttpApplicationFactory的GetApplicationInstance静态方法获取实例,然后执行该实例的BeginProcessRequest方法进行执行余下的Http Pipeline 操作,代码如下: // Get application instance IHttpHandler app = HttpApplicationFactory.GetApplication

MVC之前的那点事儿系列(1)进入CLR(转载)

MVC之前的那点事儿系列(1)进入CLR MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园多位大牛的文章,对此表示感谢,这次有时间贴出来,希望对大家有用. 主要内容 本文讲解的是:服务器接受Http Request请求之后,是如何进入.Net CLR,从而进一步操作的. 我们大家都知道,IIS必须先接受请求,然后才能有机会进入CLR,但对请求(r

MVC之前的那点事儿系列(6):动态注册HttpModule(转载)

MVC之前的那点事儿系列(6):动态注册HttpModule 文章内容 通过前面的章节,我们知道HttpApplication在初始化的时候会初始化所有配置文件里注册的HttpModules,那么有一个疑问,能否初始化之前动态加载HttpModule,而不是只从Web.config里读取? 答案是肯定的, ASP.NET MVC3发布的时候提供了一个Microsoft.Web.Infrastructure.dll文件,这个文件就是提供了动态注册HttpModule的功能,那么它是如何以注册的呢?

MVC之前的那点事儿系列(4):Http Pipeline详细分析(上)

文章内容 继续上一章节的内容,通过HttpApplicationFactory的GetApplicationInstance静态方法获取实例,然后执行该实例的BeginProcessRequest方法进行执行余下的Http Pipeline 操作,代码如下: // Get application instance IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context); 那GetApplicationIn

MVC之前的那点事儿系列(7):WebActivator的实现原理详解(转载)

MVC之前的那点事儿系列(7):WebActivator的实现原理详解 文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在HttpApplication初始化之前,之后以及ShutDown的时候分别执行指定的代码,示例如下: [assembly: WebActivator.PreApplicationStartMethod(typeof(A.InitCla

MVC之前的那点事儿系列

MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园多位大牛的文章,对此表示感谢,这次有时间贴出来,希望对大家有用. MVC之前的那点事儿系列(1):进入CLR MVC之前的那点事儿系列(2):HttpRuntime详解分析(上) MVC之前的那点事儿系列(3):HttpRuntime详解分析(下) MVC之前的那点事儿系列(4):Http Pipe