ASP.NET三剑客 HttpApplication HttpModule HttpHandler 解析

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

ASP.NET管道模型

这里以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

整体处理流程如图所示:

抽象之后的处理流程如图所示:

HttpApplication

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

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

每个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中还会讲到它们。

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

我们可以通过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对象,这就让事件注册变得十分容易了。

我在了解了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文件,可以发现这样一段配置:

这些都是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。

接下来我们创建一个自定义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>

否则会报下面的错误:

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

可以发现,我们的自定义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页面:

结果恰恰说明了: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>

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

HttpHandler

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

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

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

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图片:

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

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

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

这次效果对了,是我们想要的。关于跨域图片访问我们就不做测试了,感兴趣的话可以自己试一试。

前面我们提到了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。

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

我们再来试一下在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资源。

我们发现ASP.NET框架中已经给我们做了限定,并没有给我们任何犯错的机会!那么ASP.NET内部是怎么实现调用顺序限定的呢?我们可以通过ILSpy看一下源码。

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

总结

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

因为本人能力有限,所以文中错误难免,希望大家指正和提出宝贵建议。

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

作者:撸码那些事

来源:http://songwenjie.cnblogs.com/

声明:本文为博主学习感悟总结,水平有限,如果不当,欢迎指正。如果您认为还不错,不妨点击一下下方的【推荐】按钮,谢谢支持。转载与引用请注明出处。

微信公众号:

原文地址:https://www.cnblogs.com/songwenjie/p/9064119.html

时间: 2024-09-30 06:41:49

ASP.NET三剑客 HttpApplication HttpModule HttpHandler 解析的相关文章

HttpModule &amp; HttpHandler

ASP.NET 处理请求的过程 inetinfo.exe:www 服务进程,IIS 服务 和 ASPNET_ISAPI.dll 都寄存在此进程中. ASPNET_ISAPI.dll:处理 .aspx 文件的 win32 组件.其实,IIS 服务器只能识别 .html 文件的,当发现被请求的文件是 .aspx 文件时,IIS 服务器将其交给 aspnet_isapi.dll 来处理. aspnet_wp.exe 进程:ASP.NET 框架进程,提供 .net 运行的托管环境,CLR (公共语言运行

ASP.NET (HttpModule,HttpHandler)

asp.net 事件模型机制 ----------------------- 一 客户的请求页面由aspnet_isapi.dll这个动态连接库来处理,把请求的aspx文件发送给CLR进行编译执行,然后把Html流返回给浏览器 -----------------asp.net 事件模型机制 ----------------------- 一 客户的请求页面由aspnet_isapi.dll这个动态连接库来处理,把请求的aspx文件发送给CLR进行编译执行,然后把Html流返回给浏览器 -----

HttpHandler,HttpApplication, HttpModule

选择HttpHandler还是HttpModule? HttpHandler和HttpModule之间有什么差别 之所以有这个疑问,是因为在这二类对象中都可以访问Request, Response对象,都能处理请求. 我原以为在博客 用Asp.net写自己的服务框架 中做了那么多的演示应该把它们的使用方法说清楚了, 然而有些人看了我的那些示例,仍然不知道该如何选择它们,为了实现同一个目标,我既用了HttpHandler,也有用HttpModule. 现在看来,我当时设计的那些示例并不是讲清楚Ht

Asp.net中的HttpModule和HttpHandler的简单用法

在Asp.net中,HttpModule和HttpHandler均可以截取IIS消息进行处理,这使得我们制作人员能够非常方便的进行诸如图片水印添加,图片盗链检查等功能. 下面先就HttpModule的使用方法进行简单说明: using System;using System.Web; namespace MyWebApp{ public class MyHttpModule:IHttpModule { public void Init(HttpApplication application) {

ASP.NET 管道事件与HttpModule, HttpHandler简单理解 -摘自网络

第一部分:转载自Artech  IIS与ASP.NET管道 ASP.NET管道 以IIS 6.0为例,在工作进程w3wp.exe中,利用Aspnet_ispai.dll加载.NET运行时(如果.NET运行时尚未加载).IIS 6引入了应用程序池的概念,一个工作进程对应着一个应用程序池.一个应用程序池可以承载一个或者多个Web应用,每个Web应用映射到一个IIS虚拟目录.与IIS 5.x一样,每一个Web应用运行在各自的应用程序域中. 如果HTTP.SYS接收到的HTTP请求是对该Web应用的第一

ASP.NET 管道事件与HttpModule, HttpHandler简单理解

BeginRequest 指示请求处理开始 AuthenticateRequest 封装请求身份验证过程 AuthorizeRequest 封装检查是否能利用以前缓存的输出页面处理请求的过程 ResolveRequestCache 从缓存中得到相应时候触发 AcquireRequestState 加载初始化Session时候触发 PreRequestHandlerExecute 在Http请求进入HttpHandler之前触发 PostRequestHandlerExecute 在Http请求进

深入理解asp.net里的HttpModule机制

刚工作的时候看<asp.net深入解析>,第一次知道HttpModule和HttpHandler.当时对我而言,它们不过就是两个新名词而已,仅仅知道工作原理但是理解的不深刻.随着经验的累积,逐渐发现它们对开发的重要性.现在回头再看一遍它们的实现机制,顺便参考一下其他资源做个透彻的读书笔记.1.asp.net的HTTP请求处理过程说明:(1).客户端浏览器向服务器发出一个http请求,此请求会被inetinfo.exe进程截获,然后转交给aspnet_isapi.dll进程,接着它又通过Http

【转】.NET/ASP.NET Routing路由(深入解析路由系统架构原理)

阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4.1]UrlRoutingModule 对象内部结构 4.2]RouteBase.Route.RouteCollection.RouteTable 路由核心对象模型 4.3]RouteValueDictionary.RouteData.RequestContext 路由数据对象模型 4.4]IRou

NET/ASP.NET Routing路由(深入解析路由系统架构原理)(转载)

NET/ASP.NET Routing路由(深入解析路由系统架构原理) 阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4.1UrlRoutingModule 对象内部结构 4.2RouteBase.Route.RouteCollection.RouteTable 路由核心对象模型 4.3RouteValueDictionary.RouteData