ASP.NET三剑客 HttpApplication HttpModule HttpHandler 解析

咱们都知道,ASP.Net运行时环境中处理请求是经过一系列对象来完成的,包含HttpApplication,HttpModule, HttpHandler。之因此将这三个对象称之为ASP.NET三剑客是由于它们简直不要过重要,彻底是ASP.NET界的中流砥柱,责任担当啊。了解它们以前咱们得先知道ASP.NET管道模型。web

ASP.NET管道模型

这里以IIS6.0为例,它在工做进程w3wp.exe中会利用aspnet_isapi.dll加载.NET运行时。IIS6.0引入了应用程序池的概念,一个工做进程对应着一个应用程序池。一个应用程序池能够承载一个或多个Web应用。若是HTTP.SYS(HTTP监听器,是Windows TCP/IP网络子程序的一部分,用于持续监听HTTP请求)接收的请求是对该Web应用的第一次访问,在成功加载运行时后,IIS会经过AppDomainFactory为该Web应用建立一个应用程序域。也就是说一个应用程序池中会有多个应用程序域,它们共享一个工做进程资源,可是又不会互相牵连影响。api

随后一个特殊的运行时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请求的处理流程。bash

最终完成对HTTP请求的处理在HttpHandler中,不一样的资源类型对应着不一样类型的HttpHandler服务器

总体处理流程如图所示:网络

mark

抽象以后的处理流程如图所示:架构

mark

HttpApplication

HttpApplication是整个ASP.NET基础架构的核心,它负责处理分发给它的HTTP请求。框架

提起HttpApplication就不得不说全局配置文件global.asax。global.asax文件为每一个Web应用程序提供了一个从HttpApplication派生的Global类。该类包含事件处理程序,如Application_Start。ide

mark
ASP.NET MVC的程序入口

每一个Web应用程序都会有一个Global实例,做为应用程序的惟一入口。咱们知道ASP.NET应用程序启动时,ASP.NET运行时只调用一次Application_Start。这彷佛意味着在咱们的应用程序中只有一个Global对象实例,可是可不是只有一个HttpApplication对象实例。

ASP.NET运行时维护一个HttpApplication对象池。当第一个请求抵达时,ASP.NET会一次建立多个HttpApplication对象,并将其置于HttpApplication对象池中,而后选择其中一个对象来处理该请求。当后续请求到达时,运行时会从池中获取一个HttpApplication对象与请求进行配对。该对象与请求相关联,而且只有该请求,直到请求处理完成。当请求完成后,HttpApplication对象不会被回收,而是会返回到池中,以便稍后将其拉出为其余请求提供服务。经过使用HttpApplication对象来处理到的请求,HttpApplication对象每次只能处理一个请求,这样其成员才能够于储存针对每一个请求的数据。下面咱们来了解一下HttpApplication的成员。

前面咱们讲到过,HttpApplication对象是由HttpRunTime根据当前HTTP请求的上下文对象HttpContext建立或从池子中获取的,而且在HttpApplication初始化过程当中,ASP.NET会根据配置文件加载并初始化注册的HttpModule对象。HttpApplication中的Context属性(HttpContext(上下文)类的实例)和Modules属性(影响当前应用程序的HttpModule模块集合)就是用于存放它们的。在后面的HttpModule中还会讲到它们。

mark

HttpApplication处理请求的整个生命周期是一个相对复杂的过程,为何称之为复杂呢?由于HttpApplication类中存在大量的请求触发的事件,在请求处理的不一样阶段会触发相应的事件。

mark

咱们能够经过HttpModule注册相应的事件,将处理逻辑注入到HttpApplication处理请求的某个阶段。这里须要注意的是,从BeginRequest开始的事件,并非每一个管道事件都会被触发。由于在整个处理过程当中,随时能够调用Response.End()或者有未处理的异常发生而提早结束整个过程。全部事件中,只有EndRequest事件是确定会触发的,(部分Module的)BeginRequest有可能也不会被触发。这个咱们会在后面的HttpModule中说起。

HttpApplication类重要的Init方法和Dispose方法,这二个方法都可重载。它们的调用时机为:

Init方法在Application_Start以后调用,而Dispose在Application_End以前调用,另外Application_Start在整个ASP.NET应用的生命周期内只激发一次(好比IIS启动或网站启动时),相似的Application_End也只有当ASP.NET应用程序关闭时被调用(好比IIS中止或网站中止时)。

HttpModule

在前面咱们讲解了ASP.NET管道模型和HttpApplication对象(其中的管道事件)。如今咱们一块儿来了解一下HttpModule。

咱们都知道ASP.NET高度可扩展,那么是什么成就了ASP.NET的高度扩展性呢?HttpModule功不可没。HttpModule在初始化的过程当中,会将一些回调操做注册到HttpApplication相应的事件中,在HttpApplication请求处理生命周期的某一个阶段,相应的事件被触发,经过HttpModule注册的回调操做也会被执行。

全部的HttpModule都实现了IHttpModule接口,它和HttpApplication是直接打交道的。在其初始化方法Init()中接受了一个HttpApplication对象,这就让事件注册变得十分容易了。

mark

我在了解了HttpModule以后,不由发出一声惊叹,这不就是面向切面(AOP)嘛!!!咱们能够把HttpModule理解为HTTP请求拦截器,拦截到HTTP请求后,它能修改正在被处理的Context上下文,完事儿以后,再把控制权交还给管道,若是还有其它模块,则依次继续处理,直到全部Modules集合(前面提到过,存在于HttpApplication)中的HttpModule都“爽”完为止(可怜的HTTP请求就这样给各个HttpModule轮X了)。也正是这种相似于拦截器模式的HttpModule,配合HttpApplication管道事件给ASP.NET带来了高度可扩展性。

与HttpHandler针对某一种请求文件不一样,HttpModule则是针对全部的请求文件,映射给指定的处理程序对请求进行处理,而这些处理,能够发生在请求管线中的任何一个事件中。也就是说你订阅哪一个事件,这些处理就发生于那个事件中,处理事后再执行,你订阅过的事件的下一个事件,固然你也能够终止全部事件直接运行最后一个事件,这就意味这他能够不给HttpHandler机会。

前面两段咱们提到,HttpModule针对全部请求,处理能够发生在请求管线中的任何一个事件中。并且Modules集合中的全部HttpModule都要依次执行请求处理。这天然而然地让咱们在使用强大的HttpModule时要十分注意性能问题,须要触发哪些事件处理,不须要触发哪些事件处理,要有严格的控制。要不会让程序负重,得不偿失。

ASP.NET中内置了不少HttpModule。咱们打开C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config文件夹下的webconfig文件,能够发现这样一段配置:

mark

这些都是ASP.NET中内置的HttpModule配置。至于为何要放在这里,缘由也很简单。这里的配置都是.NET Framework的默认和基础的配置,若是要配置在每一个项目的webconfig文件中,势必会让项目的配置变得十分复杂,因此统一都放到了这里进行配置。

至于上图中的节点中的HttpModule配置的做用,咱们上面也提到过。前面咱们讲到过,在HttpApplication初始化过程当中,ASP.NET会根据配置文件加载并初始化注册的HttpModule对象。注册的HttpModule对象初始化后,存放在了HttpApplication的Modules属性之中。具体初始化哪些HttpModule对象,固然就是和这些配置相关啦。

虽然ASP.NET中内置了不少HttpModule,可是咱们能够实现自定义HttpModule给予扩展知足须要。下面咱们本身来实现一下自定义HttpModule:

首先咱们建立一个MVC5控制器DefaultController,而后在控制器中建立一个视图Index。在页面显示Hello World。

mark

接下来咱们建立一个自定义HttpModule(MyModule):

namespace WebApplication
{
    public class MyModule : IHttpModule
    {
        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(BeginRequest);
            context.EndRequest += new EventHandler(EndRequest);
        }
        void BeginRequest(object sender, EventArgs e)
        {
            ((HttpApplication)sender).Context.Response.Write("<h1>请求处理开始前进入个人Module</h1>");
        }

        void EndRequest(object sender, EventArgs e)
        {
            ((HttpApplication)sender).Context.Response.Write("<h1>请求处理结束后进入个人Module</h1>");
        }
    }
}
复制代码

咱们在初始化方法Init中对HttpApplication的管道事件BeginRequest和EndRequest分别进行了注册。注册的事件会在响应中输出不一样的文字。

最后不要忘记了在webconfig文件中进行配置,固然这个webconfig文件指的是本身项目的webconfig。咱们须要告知ASP.NET咱们有哪些须要处理的HttpModule,不然打死它他也不会知道咱们的自定义HttpModule。

这里须要的注意的是,在IIS6和IIS7经典模式中,咱们须要这样配置:

<system.web>
    <httpModules>
      <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>
    </httpModules>      
</system.web>
复制代码

type="WebApplication.MyModule,WebApplication"中的WebApplication.MyModule

指的是WebApplication命名空间下的MyModule类,后面的WebApplication是所在程序集的名称。

而在IIS7集成模式中,须要这样进行配置:

<system.webServer>
    <modules>
      <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>    
    </modules> 
</system.webServer>
复制代码

不然会报下面的错误:

mark

一切准备完毕。启动项目请求/Default/Index页面:

mark

能够发现,咱们的自定义HttpModule发挥做用了。前面咱们提到过,Modules集合(前面提到过,存在于HttpApplication)中的HttpModule在执行到相应的管道事件时都会触发本身的注册事件。咱们来试一下。

咱们再创建一个自定义HttpModule(YourModule):

namespace WebApplication
{
    public class YourModule : IHttpModule
    {
        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(BeginRequest);
            context.EndRequest += new EventHandler(EndRequest);
        }

        void BeginRequest(object sender, EventArgs e)
        {
            ((HttpApplication)sender).Context.Response.Write("<h1>请求处理开始前进入你的Module</h1>");
        }

        void EndRequest(object sender, EventArgs e)
         {
             ((HttpApplication)sender).Context.Response.Write("<h1>请求处理结束后进入你的Module</h1>");
         }    
    }
}
复制代码

而后配置webconfig告诉ASP.NET咱们又创建一个自定义HttpModule,你必定要帮我执行啊。

<system.webServer>
    <modules>
      <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>
      <add name="YourModule" type="WebApplication.YourModule,WebApplication"/>     
    </modules>
</system.webServer>
复制代码

最后启动项目请求/Default/Index页面:

mark

结果偏偏说明了:HttpModule会对请求依次进行处理,直到全部Modules集合(前面提到过,存在于HttpApplication)中的HttpModule都处理完为止

那么HttpModule会对请求进行处理的顺序是怎么控制的呢?咱们能够改变一下webconfig配置的顺序。

<system.webServer>
    <modules>
      <add name="YourModule" type="WebApplication.YourModule,WebApplication"/> 
      <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>
    </modules>
</system.webServer>
复制代码

mark

也就是说HttpModule的处理顺序,是根据配置的前后顺序来的,不存在什么优先级之说

##HttpHandler

与HttpModule针对全部的请求文件不一样,HttpHandler是针对某一类型的文件,映射给指定的处理程序对请求进行出来。换一句话说就是,对请求真正的处理是在HttpHandler中进行的,前面的处理都是打辅助。可是并非每一次请求HttpHandler都有机会接手的,辅助(HttpModule)也能够不给HttpHandler机会。

全部的HttpHandler都实现了IHttpHandler接口,其中的方法ProcessRequest提供了处理请求的实现。也就是说请求处理都是在这里面玩的,前提是辅助(HttpModule)得给机会,一会咱们也写个例子玩一玩。

mark

和HttpModule同样,HttpHandler类型创建与请求路径模式之间的映射关系,也须要经过配置文件。在C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config文件夹下的webconfig文件中,也能够找到ASP.NET内置的HttpHandler配置。

mark

ASP.NET中默认的HttpHandler映射操做发生在HttpApplication的PostMapRequestHandler事件以前触发,这种默认的映射就是经过配置。还有一种映射的方法,咱们能够调用当前HttpContext的RemapHandler方法将一个HttpHandler对象映射到当前的HTTP请求。若是未曾调用RemapHandler方法或者传入的参数是null,则进行默认的HttpHandler映射操做。须要注意的是,经过RemapHandler方法进行映射的目的就是为了直接跳过默认的映射操做,而默认的映射操做是在HttpApplication的PostMapRequestHandler事件以前触发,因此在这以前调用RemapHandler方法才有意义。

public sealed class HttpContext : IServiceProvider, IPrincipalContainer
{
   public void RemapHandler(IHttpHandler handler);  
}
复制代码

下面咱们本身写以一个自定义HttpHandler玩一玩,咱们有时候会有这么一个需求,本身的图片只但愿在本身的站点被访问到,在其余站点或浏览器直接打开都不能够正常访问。那么HttpHandler就很适合这种场景的处理,咱们以jpg格式的图片为例。

首先建立自定义HttpHandler(JPGHandler):

namespace WebApplication
{
    public class JPGHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "image/jpg";
            // 若是UrlReferrer为空,则显示一张默认的404图片  
            if (context.Request.UrlReferrer == null || context.Request.UrlReferrer.Host == null)
            {
                context.Response.WriteFile("/error.jpg");
                return;
            }
            if(context.Request.UrlReferrer.Host.IndexOf("localhost") < 0)
            {
                context.Response.WriteFile("/error.jpg");
                return;
            }
            // 获取文件服务器端物理路径  
            string fileName = context.Server.MapPath(context.Request.FilePath);

            context.Response.WriteFile(fileName);
        }
    }
}
复制代码

而后咱们在站点下面添加两张图片作测试,当图片不能够正常显示时默认展现error图片:

mark

测试搞起来,咱们在浏览器中直接请求index.jpg资源。

mark

效果不对啊,在浏览器中直接请求index.jpg资源应该是显示error图片啊。什么缘由呢?不要忘了咱们须要告诉ASP.NET咱们自定义了HttpHandler,我们没进行配置,ASP.NET固然不会知道。进行配置以后再来试试。

<system.webServer>
    <handlers>
      <add name="jpg" path="*.jpg" verb="*" type="WebApplication.JPGHandler, WebApplication" />
    </handlers>
</system.webServer>
复制代码

mark

此次效果对了,是咱们想要的。关于跨域图片访问咱们就不作测试了,感兴趣的话能够本身试一试。

前面咱们提到了HttpHandler默认的映射方式是经过配置,那么咱们再来试一试非默认的方式,经过HttpContextd的RemapHandler方法。

这又到了辅助(HttpModule)来帮忙的时候了,由于须要在HttpModule注册管道事件。前文提到在PostMapRequestHandler事件以前调用RemapHandler方法才有意义。BeginRequest事件在PostMapRequestHandler事件以前,咱们就在BeginRequest事件中调用RemapHandler方法。

namespace WebApplication
{
    public class MyModule : IHttpModule
    {
        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(BeginRequest);            
        }
        void BeginRequest(object sender, EventArgs e)
        {
            ((HttpApplication)sender).Context.RemapHandler(new JPGHandler());
        }
    }
}
复制代码

而后咱们须要在webconfig中配置MyModule,注释掉JPGHandler。

mark

最后启动项目,访问index.jpg资源,结果果真不出意外,和默认方式经过配置同样,咱们的自定义HttpHandler起到了效果。

mark

咱们再来试一下在PostMapRequestHandler事件以后调用RemapHandler方法,真的会没有意义吗?

咱们将RemapHandler方法调用放到AcquireRequestState事件中,AcquireRequestState事件是PostMapRequestHandler事件后的第一个事件。

namespace WebApplication
{
    public class MyModule : IHttpModule
    {
        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public void Init(HttpApplication context)
        {
            context.AcquireRequestState += new EventHandler(AcquireRequestState);
        }
 
        void AcquireRequestState(object sender, EventArgs e)
        {
            ((HttpApplication)sender).Context.RemapHandler(new JPGHandler());
        }
    }
}
复制代码

而后启动项目,再访问index.jpg资源。

mark

咱们发现ASP.NET框架中已经给咱们作了限定,并无给咱们任何犯错的机会!那么ASP.NET内部是怎么实现调用顺序限定的呢?咱们能够经过ILSpy看一下源码。

mark

圈红的部分,每当RemapHandler执行时,它会将当前方法所在事件(在ASP,NET管道模型中咱们提到了随着HttpContext对象的建立,HttpRunTime会利用HttpApplicationFactory建立或获取现有的HttpApplication对象,HttpApplication对象包含着一个HttpContext属性,因此是能作到这一点的)和一个枚举(以下图,对管道事件按照顺序进行了枚举编码)进行比较,若是大于或等于这个枚举(PostMapRequestHandler事件),说明是在PostMapRequestHandler事件以后进行的映射,便会抛出异常。

mark

总结

理解掌握了HttpApplication,HttpModule, HttpHandler这些并不能让咱们变得牛逼,可是ASP.NET 的管道模型和高可扩展性的实现方式却对咱们有着借鉴性的意义。再就是咱们学习必定要本身动手体验一下,不要相信任何权威,要只相信本身的双手和本身的眼睛。但愿你们看完这篇文章,脑子里能时刻记住这样一张图就OK了。

mark

由于本人能力有限,因此文中错误不免,但愿你们指正和提出宝贵建议。

参考:《ASP.NET MVC 5 框架揭秘》


                                                       -----END-----


喜欢本文的朋友们,欢迎扫一扫下图关注公众号撸码那些事,收看更多精彩内容

                                      

相关文章
相关标签/搜索