管道处理模型一

一.HTTP请求

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

管道处理模型一_第1张图片

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来进行处理,如图

管道处理模型一_第2张图片

管道处理模型一_第3张图片

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

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

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

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

管道处理模型一_第4张图片

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

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

管道处理模型一_第5张图片

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

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

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

管道处理模型一_第6张图片

7.HttpRuntime.processrequest  

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

管道处理模型一_第7张图片

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

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

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

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

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

管道处理模型一_第8张图片

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

管道处理模型一_第9张图片

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

管道处理模型一_第10张图片

它调用了ProcessRequestInternal(wr)方法:

管道处理模型一_第11张图片

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

管道处理模型一_第12张图片

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

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

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

看看HttpApplication的代码:

管道处理模型一_第13张图片

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

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

管道处理模型一_第14张图片其中BeginRequest和EndRequest是方便我们做扩展的,可以在这两个方法里面加上我们要的触发动作。

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


二.HttpModule

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

public ViewResult Events()
        {
            //获取当前上下文的HttpApplication实例
            HttpApplication app = base.HttpContext.ApplicationInstance;

            List sysEventsList = new List();
            int i = 1;
            foreach (EventInfo e in app.GetType().GetEvents())
            {
                sysEventsList.Add(new SysEvent()
                {
                    Id = i++,
                    Name = e.Name,
                    TypeName = e.GetType().Name
                });
            }
            return View(sysEventsList);
        }

运行一下,25个Event事件:

管道处理模型一_第15张图片

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

管道处理模型一_第16张图片

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

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

public class MyCustomModule : IHttpModule
    {
        public void Dispose()
        {
            //此处放置清除代码。
        }

        public void Init(HttpApplication application)
        {
            application.AcquireRequestState += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "AcquireRequestState ")); application.AuthenticateRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "AuthenticateRequest ")); application.AuthorizeRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "AuthorizeRequest ")); application.BeginRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "BeginRequest ")); application.Disposed += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "Disposed ")); application.EndRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "EndRequest ")); application.Error += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "Error ")); application.LogRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "LogRequest ")); application.MapRequestHandler += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "MapRequestHandler ")); application.PostAcquireRequestState += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostAcquireRequestState ")); application.PostAuthenticateRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostAuthenticateRequest ")); application.PostAuthorizeRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostAuthorizeRequest ")); application.PostLogRequest += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostLogRequest ")); application.PostMapRequestHandler += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostMapRequestHandler ")); application.PostReleaseRequestState += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostReleaseRequestState ")); application.PostRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostRequestHandlerExecute ")); application.PostResolveRequestCache += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostResolveRequestCache ")); application.PostUpdateRequestCache += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PostUpdateRequestCache ")); application.PreRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PreRequestHandlerExecute ")); application.PreSendRequestContent += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PreSendRequestContent ")); application.PreSendRequestHeaders += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "PreSendRequestHeaders ")); application.ReleaseRequestState += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "ReleaseRequestState ")); application.RequestCompleted += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "RequestCompleted ")); application.ResolveRequestCache += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "ResolveRequestCache ")); application.UpdateRequestCache += (s, e) => application.Response.Write(string.Format("

来自MyCustomModule 的处理,{0}请求到达 {1}


", DateTime.Now.ToString(), "UpdateRequestCache ")); } }
好了,自定义的MyCustomModule写好了,但是还不能使用,要去webconfig注册, 注册后,每个页面都会执行这个Module。

管道处理模型一_第17张图片

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

管道处理模型一_第18张图片

管道处理模型一_第19张图片管道处理模型一_第20张图片

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


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

管道处理模型一_第21张图片

web.config中有一段:

管道处理模型一_第22张图片

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

管道处理模型一_第23张图片

有没有干掉呢?看效果:

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

public ViewResult Modules()
        {
            HttpApplication app = base.HttpContext.ApplicationInstance; //获取当前上下文的HttpApplication实例
            List sysModulesList = new List();
            int i = 1;
            foreach (string name in app.Modules.AllKeys)
            {
                sysModulesList.Add(new SysModules()
                {
                    Id = i++,
                    Name = name,
                    TypeName = app.Modules[name].ToString()
                });
            }//1 我们自定义配置的config   2 来自于.Net框架的配置

            return View(sysModulesList);
        }

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

管道处理模型一_第24张图片

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

三.HttpModule 能干什么

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

2.URL转发:

新注册一个BaseModule

管道处理模型一_第25张图片

Controller添加对应方法

  public class BaseModule : IHttpModule
    {
        /// 
        /// Init方法仅用于给期望的事件注册方法
        /// 
        /// 
        public void Init(HttpApplication httpApplication)
        {
            httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始
            httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
        }
    }
       // 处理BeginRequest 事件的实际代码
        private void context_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;
            string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
            if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
            {
                context.Response.Write(string.Format("

来自BaseModule 的处理,{0}请求到达


", DateTime.Now.ToString())); } //处理地址重写 if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase)) context.RewritePath("/Pipe/Handler"); }

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

HttpApplication application = (HttpApplication)sender;
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,添加事件

管道处理模型一_第26张图片

    public class GlobalModule : IHttpModule
    {
        public event EventHandler GlobalModuleEvent;

        /// 
        /// Init方法仅用于给期望的事件注册方法
        /// 
        /// 
        public void Init(HttpApplication httpApplication)
        {
            httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始

            httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成
        }

        // 处理BeginRequest 事件的实际代码
        void context_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;
            string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
            if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
                context.Response.Write(string.Format("

来自GlobalModule 的处理,{0}请求到达


", DateTime.Now.ToString())); //处理地址重写 if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase)) context.RewritePath("/Pipe/Handler"); if (GlobalModuleEvent != null) GlobalModuleEvent.Invoke(this, e); } // 处理EndRequest 事件的实际代码 void context_EndRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; HttpContext context = application.Context; string extension = Path.GetExtension(context.Request.Url.AbsoluteUri); if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify")) context.Response.Write(string.Format("

来自GlobalModule的处理,{0}请求结束

", DateTime.Now.ToString())); } public void Dispose() { } }

注意看这里了:

public event EventHandler GlobalModuleEvent;

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

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

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

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

        /// 
        /// HttpModule注册名称_事件名称
        /// 约定的
        /// 
        /// 
        /// 
        protected void GlobalModule_GlobalModuleEvent(object sender, EventArgs e)
        {
            Response.Write("

来自 Global.asax 的文字 GlobalModule_GlobalModuleEvent

"); }

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

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

管道处理模型一_第27张图片

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

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

            Console.WriteLine("Session_End 啥也不干");
            logger.Info("Session_End");
        }

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

管道处理模型一_第28张图片

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

管道处理模型一_第29张图片

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

管道处理模型一_第30张图片

五.其他

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

你可能感兴趣的:(管道处理模型一)