隐藏

管道处理模型一

发布:2020/10/15 20:38:40作者:管理员 来源:本站 浏览次数:1043

一.HTTP请求

我们自己写的程序,是怎样进行处理的?一个完整的HTTP请求流程:

1.用户浏览器输入地址,例如 http://www.csdn.net

2.DNS解析(域名供应商):将输入的网址解析成IP+端口

3.请求到达服务器Server:IP可以在互联网上唯一定位一台服务器,而端口是用来确定进程的,端口还可以带有协议信息,用于穿过防火墙。

4.HTTP.SYS服务接收HTTP请求:

我们可以自己用IIS部署一个网站,模拟HTTP请求。顺序是部署网站----指定一个端口监听----请求到服务器----带了端口信息和协议----被HTTP.SYS监听到。HTTP.SYS是安装IIS时自动装上去的。

5.IIS将请求转发给ISAPI

IIS不能处理我们写的代码,它只能将我们的代码转发到对应的程序进行处理,它里面有一个“处理映射程序”,这里配置的是IIS的处理方式,即请求是什么后缀名,就用哪种dll处理程序进行处理,其中*.cshtml、*.aspx、*.ashx都是由asp.net_isapi.dll来进行处理,如图

因为IIS只是将请求根据后缀转发到不同的处理程序,所以我们甚至可以给java和php指定处理程序。比如将php转发给php_ISAPI,java转发给java_ISAPI,只要配置好就可以实现。

如果是js,css,html等静态文件,IIS是直接返回的。

这里有个小问题,聪明的你可能会说,像mvc这样 Home/Index,没有后缀怎么办?

IIS6和它之前都不支持mvc的,后来出现了mvc,没有后缀怎么写匹配?IIS会给没有后缀的加一个axd的后缀再处理,如图:

到了IIS7.0,就不需要这么做了。

IIS的应用程序池分为集成和经典,如图:

经典模式表明是旧的,所以一般都用集成模式,

6.HttpWorkerRequest:在上一步,会将请求包装成一个对象,通过pipeline传到这里来,这里才是asp.net开发的入口,前面是系统帮我们做好的,我们程序员是从这里开始搞事情的!

好了,终于进到我们自己写的程序中来了,看看HttpWorkerRequest的定义,如图:

7.HttpRuntime.processrequest  

HttpRuntime:Http运行时,processrequest 是它下面的一个方法,看定义,如图:

ProcessRequest 方法需要一个HttpWorkerRequest参数,也就是上一步得到的。


  1. public ActionResult Index()
  2. {
  3. HttpRuntime.ProcessRequest(null);//web请求的入口
  4. return View();
  5. }

至于ProcessRequest是怎么处理的,就要进入“管道处理模型”了。

什么是“管道处理模型”呢?就是一个请求进入到HttpRunTime之后,也就是从第7步开始,要做的事情,就叫做“管道处理模型”,因为前6步都是系统做好的,我们管不着,从第7步才开始运行我们写的代码。

我们用反编译工具打开System.Web.HttpRunTime,找到ProcessRequest

传入参数是HttpWorkerRequest类型的,如果传入null,抛出异常;如果没有使用管道模型,也抛出异常,如果都OK,就访问下一个方法 ProcessRequestNoDemand(wr)。

这里有个RequestQueue,因为Http请求也可以队列,如果队列不为空,就执行GetRequestToExecute(wr)方法进行处理,再看下面的ProcessRequestNow(wr)方法:

它调用了ProcessRequestInternal(wr)方法:

ProcessRequestInternal(wr)方法是怎么做的呢?如果被释放了(disposingHttpRuntime),那么就发送一个503的错误,"Server Too Busy",并用一个html页面来显示。如果没有被释放,就往下走,初始化一个HttpContext,它是Http请求上下文,如果初始化HttpContext失败了,就用html页面给用户返回一个400错误,下面的代码比较长,我再抓一个下面的图给大家看:

如果HttpContext初始化成功了,就把它拿到HttpApplicationFactory.GetApplicationInstance方法里面创建了一个HttpApplication对象。

每个请求都经过了上面的步骤,创建了一个HttpApplication对象,用这个HttpApplication对象来处理请求,HttpApplication是我们管道模型的核心。

通过上述步骤,终于执行到了我们写的代码,前面我们几乎没做什么,都是框架做的,我们也扩展不了。

看看HttpApplication的代码:

为什么有这么多的事件呢?因为HttpApplication要处理各种不同的请求,每个请求也许要做相同的事情,也可能不同的事情,也可能要做的事情的顺序不同,我们要把共性的部分封装起来,所以就封装成了这么多的事件(event),这样做的好处就是,遇到不同的请求,我们可以把这些事件排列和组合起来,就能完成请求的处理。

下图是对各个事件功能的介绍:

其中BeginRequest和EndRequest是方便我们做扩展的,可以在这两个方法里面加上我们要的触发动作。

PostMapRequestHandler就是把我们的请求创建一个处理器对象,我们写的MVC,Webform,都在它里面,要让它来实际执行的。


二.HttpModule

先写一段代码,用反射的方式获取所有系统自带的HttpApplication 的 Event事件:


  1. public ViewResult Events()
  2. {
  3. //获取当前上下文的HttpApplication实例
  4. HttpApplication app = base.HttpContext.ApplicationInstance;
  5. List<SysEvent> sysEventsList = new List<SysEvent>();
  6. int i = 1;
  7. foreach (EventInfo e in app.GetType().GetEvents())
  8. {
  9. sysEventsList.Add(new SysEvent()
  10. {
  11. Id = i++,
  12. Name = e.Name,
  13. TypeName = e.GetType().Name
  14. });
  15. }
  16. return View(sysEventsList);
  17. }

运行一下,25个Event事件:

先说一下,HttpModule是什么:对HttpApplication做的扩展事件(上面图上,都是HttpApplication原本有的事件)。先看一下IHttpModule接口里面是什么:

很简单对不对?Init方法,参数是HttpApplication。接下来,我们就做对IHttpModule做一个实现吧:

新建一个MyCustomModule,继承自IHttpModule,具体做的事情就是给原有事件输出一段文字。


  1. public class MyCustomModule : IHttpModule
  2. {
  3. public void Dispose()
  4. {
  5. //此处放置清除代码。
  6. }
  7. public void Init(HttpApplication application)
  8. {
  9. application.AcquireRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AcquireRequestState "));
  10. application.AuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AuthenticateRequest "));
  11. application.AuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AuthorizeRequest "));
  12. application.BeginRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "BeginRequest "));
  13. application.Disposed += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "Disposed "));
  14. application.EndRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "EndRequest "));
  15. application.Error += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "Error "));
  16. application.LogRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "LogRequest "));
  17. application.MapRequestHandler += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "MapRequestHandler "));
  18. application.PostAcquireRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAcquireRequestState "));
  19. application.PostAuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAuthenticateRequest "));
  20. application.PostAuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAuthorizeRequest "));
  21. application.PostLogRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostLogRequest "));
  22. application.PostMapRequestHandler += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostMapRequestHandler "));
  23. application.PostReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostReleaseRequestState "));
  24. application.PostRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostRequestHandlerExecute "));
  25. application.PostResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostResolveRequestCache "));
  26. application.PostUpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostUpdateRequestCache "));
  27. application.PreRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreRequestHandlerExecute "));
  28. application.PreSendRequestContent += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreSendRequestContent "));
  29. application.PreSendRequestHeaders += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreSendRequestHeaders "));
  30. application.ReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "ReleaseRequestState "));
  31. application.RequestCompleted += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "RequestCompleted "));
  32. application.ResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "ResolveRequestCache "));
  33. application.UpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "UpdateRequestCache "));
  34. }
  35. }
好了,自定义的MyCustomModule写好了,但是还不能使用,要去webconfig注册, 注册后,每个页面都会执行这个Module。

运行一下,发现注册后比以前多了一些东西:

MyCustomModule被从头到尾执行了一遍,这些蓝色的文字,说明在这个module中执行了哪些事件以及执行的顺序,而以前通过反射找到HttpApplication中事件(ShowEvents方法),是在PreRequstHandlerExecute方法中执行的,执行完后,还进行了收尾操作,比如PostRequestHandlerExecute(已经执行了处理程序),ReleaseRequestState(释放请求的状态),PostReleaseRequestState(已经释放了请求的状态)等等。


好了,上面就是我们自定义的Module,现在看看框架自带的Module,打开

web.config中有一段:

看到没?有OutputCache,Session,WindowsAuthentication等,这些都是.net4.0框架自己注册好了的Module,是全局的,运行在这台服务器上的Web程序都要用到这个config,每个页面用到了这些Module。但是这里列的一些Module我们是用不上的,比如WindowsAuthentication,FormsAuthentication,PassportAuthentication,可以干掉的,不过不要在这个全局的webconfig中干掉,可以在每个项目的webconfig中干掉,比如刚才项目的webcongif中可以加这么一些代码干掉不要的Module:

有没有干掉呢?看效果:

写一个方法,获取全部的Module,包括我们扩展的MyCustomModule,还有框架自带的


  1. public ViewResult Modules()
  2. {
  3. HttpApplication app = base.HttpContext.ApplicationInstance; //获取当前上下文的HttpApplication实例
  4. List<SysModules> sysModulesList = new List<SysModules>();
  5. int i = 1;
  6. foreach (string name in app.Modules.AllKeys)
  7. {
  8. sysModulesList.Add(new SysModules()
  9. {
  10. Id = i++,
  11. Name = name,
  12. TypeName = app.Modules[name].ToString()
  13. });
  14. }//1 我们自定义配置的config 2 来自于.Net框架的配置
  15. return View(sysModulesList);
  16. }

运行一下,发现在webconfig中remove的Module,它们都不出现了:

总结一下,HttpModule要实现IHttpModule接口,在webconfig注册,里面给HttpApplication事件去添加动作,并且HttpModule是每个页面(每次请求)都要执行的。

三.HttpModule 能干什么

1.权限认证:每个请求都经过Module,所以做权限认证很好。

2.URL转发:

新注册一个BaseModule

Controller添加对应方法


  1. public class BaseModule : IHttpModule
  2. {
  3. /// <summary>
  4. /// Init方法仅用于给期望的事件注册方法
  5. /// </summary>
  6. /// <param name="httpApplication"></param>
  7. public void Init(HttpApplication httpApplication)
  8. {
  9. httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
  10. httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
  11. }
  12. }

  1. // 处理BeginRequest 事件的实际代码
  2. private void context_BeginRequest(object sender, EventArgs e)
  3. {
  4. HttpApplication application = (HttpApplication)sender;
  5. HttpContext context = application.Context;
  6. string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
  7. if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
  8. {
  9. context.Response.Write(string.Format("<h4 style='color:#00f'>来自BaseModule 的处理,{0}请求到达</h4><hr>", DateTime.Now.ToString()));
  10. }
  11. //处理地址重写
  12. if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase))
  13. context.RewritePath("/Pipe/Handler");
  14. }

注意这两句,先获取到了HttpApplication,后来又获取到了HttpContext,有了HttpContext就有了全世界。


  1. HttpApplication application = (HttpApplication)sender;
  2. HttpContext context = application.Context;

接下来如果访问 "/Pipe/Some",Module会偷偷跳到"/Pipe/Handler",而且浏览器的地址栏不改变,还是"/Pipe/Some"。

MVC其实就是Module的扩展,即上面说过的UrlRoutingModule, MVC框架的诞生对webform没有影响,只是在原来框架流程上增加了一个UrlRoutingModule,它会把我们的请求做一次映射处理,指向MvcHandler,MVC路由为什么能生效啊?就是利用了UrlRoutingModule,处理方式和上面说的Url转发是一样的。

伪静态也可以这样做,先在IIS里面给后缀指定处理程序asp.net_isapi(上面有介绍),请求进到HttpApplication后,我们扩展一个Module,在里面建立一些跳转页面的规则。

3.反爬虫:

每个请求都要经过Module,所以可以记录每个IP,如果某个IP请求太频繁,那么就不让它访问原始页面,让它访问一个需要输入验证码的页面,验证通过了才能继续访问。

但也有Module不适合的场景,比如对一些特殊请求的处理。Module是每个请求都会执行的,单独给某些请求服务的不合适。

又总结一下,管道处理模型就是请求进入System.Web(HttpRuntime.processrequest)之后,那些处理的类、方法、过程,这些就叫管道处理模型,mvc只是其中很小的一部分。

四.Module的扩展

新注册一个GlobalModule,添加事件


  1. public class GlobalModule : IHttpModule
  2. {
  3. public event EventHandler GlobalModuleEvent;
  4. /// <summary>
  5. /// Init方法仅用于给期望的事件注册方法
  6. /// </summary>
  7. /// <param name="httpApplication"></param>
  8. public void Init(HttpApplication httpApplication)
  9. {
  10. httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
  11. httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
  12. }
  13. // 处理BeginRequest 事件的实际代码
  14. void context_BeginRequest(object sender, EventArgs e)
  15. {
  16. HttpApplication application = (HttpApplication)sender;
  17. HttpContext context = application.Context;
  18. string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
  19. if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
  20. context.Response.Write(string.Format("<h1 style='color:#00f'>来自GlobalModule 的处理,{0}请求到达</h1><hr>", DateTime.Now.ToString()));
  21. //处理地址重写
  22. if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase))
  23. context.RewritePath("/Pipe/Handler");
  24. if (GlobalModuleEvent != null)
  25. GlobalModuleEvent.Invoke(this, e);
  26. }
  27. // 处理EndRequest 事件的实际代码
  28. void context_EndRequest(object sender, EventArgs e)
  29. {
  30. HttpApplication application = (HttpApplication)sender;
  31. HttpContext context = application.Context;
  32. string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
  33. if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
  34. context.Response.Write(string.Format("<hr><h1 style='color:#f00'>来自GlobalModule的处理,{0}请求结束</h1>", DateTime.Now.ToString()));
  35. }
  36. public void Dispose()
  37. {
  38. }
  39. }

注意看这里了:

public event EventHandler GlobalModuleEvent;

本来Module是用来添加事件的,比如:

httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始

这里结果又给Module配置了一个事件GlobalModuleEvent。

怎样让这个GlobalModuleEvent生效呢?在global里面添加代码:


  1. /// <summary>
  2. /// HttpModule注册名称_事件名称
  3. /// 约定的
  4. /// </summary>
  5. /// <param name="sender"></param>
  6. /// <param name="e"></param>
  7. protected void GlobalModule_GlobalModuleEvent(object sender, EventArgs e)
  8. {
  9. Response.Write("<h3 style='color:#800800'>来自 Global.asax 的文字 GlobalModule_GlobalModuleEvent</h2>");
  10. }

其中方法名 = webconfig中注册的Module名称 + ‘_’+ Module中的事件名称

这样,这个方法就会自动触发,如图:

有没有类似的?有的,大家应该见过这个,也是在global里面


  1. protected void Session_Start(object sender, EventArgs e)
  2. {
  3. // 在新会话启动时运行的代码
  4. Console.WriteLine("Session_Start 啥也不干");
  5. logger.Info("Session_Start");
  6. }
  7. protected void Session_End(object sender, EventArgs e)
  8. {
  9. // 在会话结束时运行的代码。
  10. // 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为
  11. // InProc(默认内存里) 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer
  12. // 或 SQLServer,则不会引发该事件。
  13. Console.WriteLine("Session_End 啥也不干");
  14. logger.Info("Session_End");
  15. }

Start和End就是Session这个Module里面定义的Event。有证据吗?有的!先看看全局的webconfig,session注册过。

然后通过反编译工具,找到SessionState这个类,从它内部找到了Start和End。

这样做的好处是:我们封装了Module之后,Module里面还有要扩展的,就可以做成Event,在global里面去实现,并且也只能在global里面实现,什么时候执行这个Event呢?根据自己的需求。使用套路如图:

五.其他


  1. /// <summary>
  2. /// 请求出现异常,都可以处理
  3. /// 也可以完成全局异常处理
  4. /// filter只能处理控制器里面的异常
  5. /// </summary>
  6. /// <param name="sender"></param>
  7. /// <param name="e"></param>
  8. protected void Application_Error(object sender, EventArgs e)
  9. {
  10. logger.Info("Application_Error");
  11. Response.Write("出错");
  12. Server.ClearError();
  13. }
Application_Error是处理异常的,filter也是处理异常的,但前提是要进入了控制器,如果是cshtml出错了,或者页面不存在,filter就管不到了,但可以被Application_Error处理,它可以捕获整个网站的异常,不管是控件级事件、页面级事件还是请求级事件,都可以get到。