我相信在第一次听到这个名词时,有的小伙伴会一脸懵,而且还有很多疑问,其实我在第一次接触这个概念时跟很多小伙伴一样一脸懵.

接下来我将以我自己的理解来讲述什么是管道模型。

什么是管道模型

首先有没有小伙伴思考过在.NET 中WebForm、MVC、Webapi 之类的框架做应用程序的开发部署之后,用户在浏览器或是利用HttpClient 输入URL地址请求之后是怎么到达我们的应用程序呢?中间又经过了哪些步骤呢?

其实在用户输入URL之后,Http请求会经过一系列的流转,每次流转都有一些特有的处理,而管道模型就是为Http请求提供支撑和流转而抽象出的一个模型

管道由托管模块(Managed Modules)和处理程序(Handlers)组成且由管道处理 Http请求。这就是asp管道模型。

5. ASP.NET 管道模型简析 - 图1

管道模型是整个asp.net的核心。 其中最重要的就是HttpModule和HttpHandler。 HttpModule,可以看做是一个拦截器,给我们在特定的事件处理请求的机会。HttpModule有很多应用,例如,我们要在每个请求的页面事件前加载Session数据,那么就用到SessionModule等等;asp.net4.0提供了路由机制也是建立在一个UrlRouteModule上面的,它在请求映射到具体程序前拦截,然后重新映射。MVC框架又是建立在路由机制的基础上的。 HttpHandler,可以看做一个处理器,它负责处理请求,输出数据。aspx,ashx或者说实现了IHttpHandler的都是HttpHandler。 至于为什么是19个事件,这是按照功能划分的,让你可以在请求各个阶段做处理。以前没有19个事件,以后还会划分更多。

在实际中他是如何执行的

老规矩先上图,然后再一步一步分析,这样比较直观,其实在isapi之前还有一些只是在流程图中没有标记出来,因为没必要去深究,至少在目前阶段.

5. ASP.NET 管道模型简析 - 图2

下面以文字的方式来解释一下,管道模型一部分的流程图走向情况:

  1. 用户发起请求到达DNS,DNS会解析域名找到对应的IP及端口。
  2. IIS中HttpSys监听服务接受请求。
  3. HttpSys监听服务根据请求类型的后缀,将请求转发到对应的应用程序处理(isapi.dll),IIS中处理程序映射可以实现配置,不同的后缀将到达不同的处理程序.net的请求映射到aspnet.isapi,java或者其他的也可以配置对应的dll。
  4. 在http请求到达Isapi后会将请求转换为一个HttpWorkerRequest对象,然后把对象传入到 HttpRuntime.ProcessRequest
  5. 在ProcessRequest方法中利用HttpApplicationFactory传入HttpContext进行构建 HttpApplication,当然在这前几个步骤如果发生错误,那就直接返回了。

我上面描述的这一部分已经执行完了,当然由于是系统固定流程这时作为开发者并不能进行升级或者是扩展,待请求到达了我们被创建的HttpApplication之后的才是真正的开始.


HttpApplication

HttpApplication类在管道模型中充当一个很重要的角色,它定义对 ASP.NET 应用程序内所有应用程序对象公用的方法、属性和事件,此类是用户在 Global.asax 文件中定义的应用程序的基类,订阅httpApplication事件的任何 HTTP 模块 (HttpModule) 都必须实现 IHttpModule接口.

在我们学习管道的过程中,所注重的应该是它所发布的一系列事件,它们用来处理各式各样的请求,并且每次请求都会将它们逐一执行一遍,但由于处理不尽相同,又可能都需要,所以使用了事件的模式来扩展,具体事件内容和作用,在下方引入的代码中.

开闭原则

观察者模式

** (用事件的意义:封装共性部分,将不确定部分利用事件的方式,说白了就是委托回调,将需要扩展的部分以委托的形式暴露出去,委托对于C#,跟指针对于C一样的伟大,一样的令人膜拜)**

  1. public class HttpApplication : IComponent,IDisposable,
  2. IHttpAsyncHandler, IHttpHandler,
  3. IRequestCompletedNotifier, ISyncContext
  4. {
  5. //表示处理的开始
  6. public event EventHandler BeginRequest;
  7. //验证请求,一般用来取得请求用户的信息
  8. public event EventHandler AuthenticateRequest;
  9. //已经获取请求用户的信息
  10. public event PostAuthenticateRequest;
  11. // 授权,一般用来检查用户的请求是否获得权限
  12. public event AuthorizeRequest;
  13. //用户请求已经得到授权
  14. public event PostAuthorizeRequest;
  15. //获取以前处理缓存的处理结果,如果以前缓存过,那么不必再进行请求的处理,直接返回缓存结果
  16. public event ResolveRequestCache;
  17. //已经完成缓存的获取操作
  18. public event PostResolveRequestCache;
  19. //已经根据用户的请求,创建了处理请求的处理器对象
  20. public event PostMapRequestHandler;
  21. //取得请求的状态,一般用于Session
  22. public event AcquireRequestState;
  23. //已经取得了Session
  24. public event PostAcquireRequestState;
  25. //准备执行处理程序
  26. public event PreRequestHandlerExecute;
  27. //已经执行了处理程序
  28. public event PostRequestHandlerExecute;
  29. //释放请求的状态
  30. public event ReleaseRequestState;
  31. //已经释放了请求的状态
  32. public event PostReleaseRequestState;
  33. //更新缓存
  34. public event UpdateRequestCache;
  35. // 已经更新了缓存
  36. public event PostUpdateRequestCache;
  37. //请求的日志操作
  38. public event LogRequest;
  39. //已经完成了请求的日志操作
  40. public event PostLogRequest;
  41. 本次请求处理完成
  42. public event EndRequest;
  43. }

HttpModule

疑问又来了,既然HttpApplication发布了一系列事件,我想对其发布某个事件进行扩展或者自定义又该如何操作呢?接下来就该HttpModule登场了,没错他才是主角,在ASP.NET中占有举足轻重的地位,可以说是大哥级别的,因为无论是WebForm,又或者是MVC乃至WebAPI都只是HttpModule延伸出来的冰山一角而已,这么说在某种意义上一点也不过分,只是突然感觉自己很渺小.

突然这样说可能让人无法理解,当你自己去理解了,你肯定会赞同我这种说法.

由于每一次Http请求,都会将HttpApplication中所有的事件执行一遍,同理那实现订阅的HttpModule也会针对每一个请求而执行一次,所以比较适合做一些全局的操作,例如缓存,或者实现请求压缩,做一些性能监控,或者恶意ip请求校验,下面用一个实例来实现HttpModule请求压缩,以及实现的步骤

  • 1.首先创建一个类GzipModule 实现IHttpModule接口
  1. internal class GzipModule : IHttpModule
  2. {
  3. public void Dispose(){}
  4. public void Init(HttpApplication app)
  5. {
  6. app.BeginRequest += App_BeginRequest;
  7. }
  8. private void App_BeginRequest(object sender, EventArgs e)
  9. {
  10. HttpApplication app = (HttpApplication)sender;
  11. string IsAcceptType = app.Context.Request.Headers["Accept-Encoding"];
  12. //请求头不包含格式编码直接拦截返回
  13. if (string.IsNullOrEmpty(IsAcceptType)) return;
  14. IsAcceptType = IsAcceptType.ToUpperInvariant();
  15. //如果客户端请求头包含压缩请求标识那么响应也返回压缩格式
  16. //如果是默认请求就返回默认的响应格式
  17. if (IsAcceptType.Contains("GZIP"))
  18. {
  19. app.Context.Response.AppendHeader("Content-encoding", "gzip");
  20. app.Context.Response.Filter = new GZipStream(app.Context.Response.Filter
  21. , CompressionMode.Compress);
  22. }
  23. else if (IsAcceptType.Contains("DEFLATE"))
  24. {
  25. app.Context.Response.AppendHeader("Content-encoding", "deflate");
  26. app.Context.Response.Filter = new DeflateStream(app.Context.Response.Filter
  27. , CompressionMode.Compress);
  28. }
  29. }
  30. }
  • 2.在配置文件下注册HttpModule
  1. <system.webServer>
  2. <httpErrors existingResponse="PassThrough" />
  3. <modules>
  4. <add name="GzipModule" type="namespace.GzipModule, namespace" preCondition="integratedMode" />
  5. </modules>
  6. </system.webServer>
  1. 到这里您可能会问有没有动态注册的方式不写在配置文件,我的回答是有的,以webapi框架为例

1.在AssemblyInfo文件中加入编译特性,在代码编译时动态注册

  1. [assembly: PreApplicationStartMethod(typeof(DynamicRegisterHttpMoudle), "RegisterMoudle")]
  2. public class DynamicRegisterHttpMoudle
  3. {
  4. public static void RegisterMoudle()
  5. {
  6. HttpApplication.RegisterModule(typeof(GzipModule));
  7. }
  8. }

2.利用静态构造函数注入,因为静态构造函数是在类加载时首先被CLR调用的

  1. public class WebApiApplication : System.Web.HttpApplication
  2. {
  3. static WebApiApplication(){
  4. RegisterModule(typeof(GzipModule));
  5. }
  6. protected void Application_Start() { }
  7. }

到这里你可能仍然无法理解HttpModule,只是简单知道它是什么和它的表象,最重要的一点就是任何Http请求都会经过它,但是我觉得知道这一点就够了,相信随着后面的积累,你对它将会对它有颠覆性的认识,并且崇拜它.


HttpHandler

接下来我们还要认识一个重要的内容HttpHandler,至于它是什么?相对正式的回答是这样的:

HttpHandler是一个HTTP请求的真正处理中心,也正是在这个HttpHandler容器中,ASP.NET Framework才真正地对客户端请求的服务器页面做出编译和执行并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。

不过从字面意思还是能看得懂的,至于大白话的意思就是Http最终的请求是由这个叫HttpHandler来完成的,到底是不是这样呢?在后面我会进一步去验证,先来做一个HttpHandler简单的实现,我们自定义一个后缀为.cc请求的被我们自定义的HttpHandler处理。

1.首先自定义类然后继承自IHttpHandler

  1. public class CustomHttpHander : IHttpHandler
  2. {
  3. public bool IsReusable => false;
  4. public void ProcessRequest(HttpContext context)
  5. {
  6. string time =DateTime.Now.ToString();
  7. string respone = string.Format("自定义HttpHander,{0}请求到达", time );
  8. context.Response.Write(respone);
  9. }
  10. }

2.配置文件注册HttpHandler

  1. <system.webServer>
  2. <httpErrors existingResponse="PassThrough" />
  3. <modules>
  4. <add name="CustomHttpHandler" path="*.cc" verb="*" type="namespace.CustomHttpHander,namespace" preCondition="integratedMode,runtimeVersionv4.0" />
  5. </modules>
  6. </system.webServer>

这样我们就自定义了一个.cc后缀的请求被CustomHttpHandler处理,至于为什么能实现特殊请求后缀的自定义,这是HttpHandler的机制,但是它的核心内容还是一个Http请求处理中心,当然我们后续会进一步了解的MvcHandler 、HttpControllerHandler这些meb框架中的核心处理程序,从源码的角度来更进一步的了解它。

至此管道模型大致的概念以及执行流程已经在本文中做了简单分析,后续学习Mvc和Webapi的源码因为和管道这一块衔接很紧密,有了这一部分的铺垫及知识点画像,相对会更加容易理解。