前言:做为一个开发人员,咱们看过不少的关于开发的书,可是都是教咱们"知其然",并无教咱们"知其因此然",咱们开发web项目的过程当中,当咱们输完URL敲下回车就跳到咱们的网站,咱们知道这背后浏览器以及IIS所作的工做嘛?咱们只有理解底层的运做原理,才有可能作一个更加优秀的开发人员。
本篇随笔参考了HANS许的 《ASP.NET/MVC/Core的HTTP请求流程》 与张子阳的《HttpHandler介绍》、《HttpModule介绍》,双魂人生的《HttpModule和在Global.asax区别》。html
咱们在开发一个web页面时,大都时候都是去思考页面及处理的逻辑,不多去考虑请求的过程,也就不知道了这个过程里IIS与跳到业务代码以前的代码是怎么跑的。
那咱们有没有可能经过代码去操控http请求呢?
答案是确定的。framework给咱们提供了两个主要的接口来实现这一操做,这两个接口就是IHttpHandler与HttpModule,咱们在HANDS许的博客中已经知道,ISAPI根据文件名后缀把不一样的请求转交给不一样 的处理程序,咱们打开IIS,点击任意一个网站,再点击"处理程序映射"(IIS低版本下在网站鼠标右击属性,而后选择“主目录”选项,接着选择“配置”),咱们能发现大部分的后缀都是经过aspnet_isapi.dll去处理的,可是这个程序不可能对不一样的文件采用同一种处理方式,那它是怎么处理的呢?
咱们能够打开C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config此目录底下的webconfig文件,咱们能够找到如下的代码:web
<httpHandlers> <add path="eurl.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True" /> <add path="trace.axd" verb="*" type="System.Web.Handlers.TraceHandler" validate="True" /> <add path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader" validate="True" /> <add verb="*" path="*_AppService.axd" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False" /> <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False"/> <add path="*.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True" /> <add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True" /> <add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True" /> <add path="*.asmx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False" /> <add path="*.rem" verb="*" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="False" /> <add path="*.soap" verb="*" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="False" /> <add path="*.asax" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" /> ...... </httpHandlers>
在httpHandlers这个节点中,将不一样的文件映射给不一样的Handler去处理,那咱们若是要去操控http请求,咱们也能够学着他去实现IHttpHandler接口,写咱们所须要的代码。
那咱们如今来尝试一下怎么去实现这个需求作一个图片防盗链的例子。ajax
①.由于要实现一个防盗链的程序,咱们须要先去处理传过来的http请求,判断是否是我方的,是的话返回正确的图片,不是的话返回一张错误的图片。两张图片以下:
正确图片(一张风景图):
sql
错误的图片(禁止COPY): <img src="https://img2018.cnblogs.com/blog/1423782/201812/1423782-20181204083058019-383669919.jpg" style="width:300px;height:300px">api
因此,咱们先建一个专门处理特殊请求的类库HandlerLib,建一个处理后缀名为jpg的请求的类,而后去继承IHttpHandler接口,代码以下。浏览器
namespace HandlerLib { /// <summary> /// 处理后缀名为jpg的请求 /// </summary> public class JpgHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { string FileName = context.Server.MapPath(context.Request.FilePath); if (context.Request.UrlReferrer.Host == null) { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/error.jpg"); } else { //此处的可填你网站的域名,由于我这里采用的是本机演示,故使用localhost if (context.Request.UrlReferrer.Host.IndexOf("localhost") !=-1) { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile(FileName); } else { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/error.jpg"); } } } public bool IsReusable { get { return true; } } } }
写到这里咱们就能发现,这不就是通常处理程序嘛,聪明的同窗一会儿就懂了,平时咱们用ajax去请求或实现验证码功能时就是一种特殊的请求,是须要由咱们本身来定义的,而不是让框架帮咱们去作。 ②、接下来,咱们学着框架提供的例子到webconfig中的system.web节点下注册一个httpHandlers,那咱们这里注册一个后缀名为jpg的。 缓存
咱们这里新建一个index.aspx页面,放一个img标签来模拟其余网站对咱们网站图片的盗用
安全
而后启动网站访问这个页面。访问的页面以下:
服务器
咱们发现并无获得咱们想要的效果,访问的仍是正常的页面。通过去调试,我发现代码也没有跳到那个handler里面,通过查阅相关资料(微软官方MSDN文档),发现框架提供的那种注册写法是针对IIS7如下的,因为我本机是IIS10,因此注册的节点应该在system.webServer。名称也不是HttpHandle而是handler,详情以下图: session
此处有一个注意点handler节点比HttpHandle节点多了一个必填属性name,这个属性是由咱们本身填的,但不可重复。
咱们再次启动index.aspx页面。这次终于达到了咱们想要的效果了。如图:
固然,这个例子比较简陋,真正的防盗链有好多种,此例子只是为了帮助咱们更好的去理解Http请求的过程当中HttpHandler的做用。
写到了这里,咱们又去联想,若是多个请求类型能不能写成工厂模式,统一一个入口呢?
咱们去查找相关资料,这个时候,咱们发现 IHttpHandlerFactory这个接口能够来帮助咱们完成这一过程。其定义以下图:
接下来咱们进行一个尝试,显然,要实现GetHandler()与ReleaseHandler()方法,微软官方文档上有对其介绍,对此有疑问的同窗可移步去学习一下。
咱们只要新增一个统一的处理类便可。
/// <summary> /// Handler处理工厂 /// </summary> public class HandlerFactory : IHttpHandlerFactory { public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) { string path = context.Request.PhysicalPath; if (Path.GetExtension(path) == ".jpg") { return new JpgHandler(); } if (Path.GetExtension(path) == ".js") { return new JsHandler(); } return null; } public void ReleaseHandler(IHttpHandler handler) { } }
咱们查看IIS的处理程序映射,咱们发现请求路径的示例中能够用逗号将请求的不一样后缀名分隔开,处理程序可为同一个,那咱们进行尝试,webconfig的配置为:
<handlers> <add name="factorhandler" path="*.js,*.jpg" verb="*" type="HandlerLib.HandlerFactory" /> </handlers>
通过尝试,咱们发现无效,可是分开定义是没有问题的。
<handlers> <add name="jpghandler" path="*.jpg" verb="*" type="HandlerLib.HandlerFactory"/> <add name="jshandler" path="*.js" verb="*" type="HandlerLib.HandlerFactory"/> </handlers>
Tips: 若是有清楚IIS的处理程序映射的路径怎么同时设置两种的同窗,欢迎在评论区留言指导,本人将不胜感激!
在ASP.NET MVC中也是定义了MVCHandler来作一些MVC特有的动做,举一个最简单的例子,咱们要如何判断一个网站是采用ASP.NET仍是ASP.NET MVC,最简单的就是打开F12,查看网页的相应头部: 咱们能够看到以下页面:
从图中咱们便能很清楚知道MVC的版本是什么。
接下来咱们去看下源码是如何写的,有须要源码的同窗可前往下载(ASP.NET MVC源码),笔者这里是MVC4.0的源码:
其实就是往相应头部增长一个X-AspNetMvc-Version字段,固然这个MVCHandler还作了不少事情,这里就不一一例举了。相似于HTTPHandler的用法还有不少,有兴趣的同窗能够自行学习下。
咱们在HANS许的博客中知道,在Http请求由IHttpHandler处理以前,它须要经过一系列的HttpModule,在请求处理以后,须要在经过一系列的HttpModule。
与注册HttPHandler相似,咱们先看下框架内的示例,一样是C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config此目录底下的webconfig文件(咱们给每一个module加了注释):
<httpModules> //页面级输出缓存 <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" /> //Session 状态管理 <add name="Session" type="System.Web.SessionState.SessionStateModule" /> //用 集 成 Windows身份验证进行客户端验证 <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" /> //用基于 Cookie 的窗体身份验证进行客户端身份验证 <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" /> //用 MS 护照进行客户身份验证 <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" /> //管理当前用户角色 <add name="RoleManager" type="System.Web.Security.RoleManagerModule" /> //判断用户是否被受权访问某一URL <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" /> //判断用户是否被受权访问某一资源 <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" /> //管理 Asp.Net 应用程序中的匿名访问 <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" /> //管理用户档案文件的创立 及相关事件 <add name="Profile" type="System.Web.Profile.ProfileModule" /> //捕捉异常,格式化错误提示字符,传递给客户端程序 <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> //提供类,与服务模型相关。 <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> //将 URL 请求与定义的路由进行匹配。 <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /> //管理用于 ASP.NET 中 AJAX 功能的 HTTP 模块。 <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </httpModules>
接下来咱们来看下如何去实现IHttpModule接口:
咱们首先看下在元数据中这个接口的定义
很明显,由此定义,当站点第一个资源被访问的时候,Asp.Net 会建立 HttpApplication 类的实例,它表明着站点应用程序,同时会建立全部在 Web.Config 中注册过的 Module 实例。在建立每一个Module时去调用Init()方法,这时咱们就要在这个方法里注册咱们本身的方法
/// <summary> /// module /// </summary> public class ModuleService : IHttpModule { public ModuleService() { } public string ModuleName { get { return "ModuleDemo"; } } public void Init(HttpApplication application) { application.BeginRequest +=(new EventHandler(this.Application_BeginRequest)); application.EndRequest +=(new EventHandler(this.Application_EndRequest)); } private void Application_BeginRequest(Object source,EventArgs e) { HttpApplication application = (HttpApplication)source; HttpContext context = application.Context; context.Response.Write("<h1><font color=red>" +"ModuleDemo: 请求开始" +"</font></h1><hr>"); } private void Application_EndRequest(Object source, EventArgs e) { HttpApplication application = (HttpApplication)source; HttpContext context = application.Context; context.Response.Write("<hr><h1><font color=red>" + "ModuleDemo: 结束请求</font></h1>"); } public void Dispose() { } }
咱们新建个页面,如图:
打开浏览器访问此页面,获得此页面:
接下来咱们举个"每一个页面验证是否登陆"的例子: 首先,咱们仍是在Init方法先注册一个PreRequestHandlerExecute事件,此事件是刚好在 ASP.NET 开始执行事件处理程序(例如,某页或某个 XML Web services)前发生。具体的能够去微软官方MSDN 查看:
而后咱们建一个登陆的页面,将登陆事后的user保存到session。代码以下:
aspx页面:
<form id="form1" runat="server"> <div style="width:300px;margin: 10% 40%;text-align:right;"> <div> 用户名:<input placeholder="请输入用户名" id="user" runat="server" /> </div> <div style="margin-top:20px;"> <button runat="server" onserverclick="Unnamed_ServerClick">提交</button> </div> </div> </form>
aspx.cs页面:
protected void Unnamed_ServerClick(object sender, EventArgs e) { Session["user"] = user.Value; if (Request.QueryString["source"] != null) { string s = Request.QueryString["source"].ToLower().ToString(); //取出从哪一个页面转来的 Response.Redirect(s); //转到用户想去的页面 } else { Response.Redirect("index.aspx"); //默认转向index.aspx } }
经过运行这个例子,咱们就能发现,每次请求以前咱们都会去查session中是否有user信息,没有的话就回重定向到登陆页。
在ASP.NETMVC中,即是定义了一个UrlRoutingModule建立了路由系统(在介绍httpmodule初始,咱们已经介绍了这个httpmodule已经在framework中已经默认注册了),从Http请求获取Controller和Action以及路由数据。接着匹配Route规则,获取Route对象,解析对象。UrlRoutingModule这个在System.Web.Routing程序集中,有兴趣的同窗能够尝试去反编译下,看看源码。
经过以上的描述,咱们也能很清楚的知道httpmodule在整个http请求中的做用。
Global.asax文件咱们很熟悉,可是其用法也知道的很少,那咱们先来了解下这个文件,再探究下它与HttModule的区别。
global.asax是一个文本文件,它提供全局可用代码。这些代码包括应用程序的事件处理程序以及会话事件、方法和静态变量。有时该文件也被称为应用程序文件。 那其内部方法都有哪些呢,执行顺序是怎样的?
Application_Init 和Application_Start 事件在应用程序第一次启动时被触发一次。类似地,Application_Disposed 和 Application_End 事件在应用程序终止时被触发一次。此外,基于会话的事件(Session_Start 和 Session_End)只在用户进入和离开站点时被使用.其他的事件则处理应用程序请求,这些事件被触发的顺序是:
Application_BeginRequest:在接收到一个应用程序请求时触发。对于一个请求来讲,它是第一个被触发的事件,请求通常是用户输入的一个页面请求(URL)。
Application_AuthenticateRequest:在安全模块创建起当前用户的有效的身份时,该事件被触发。在这个时候,用户的凭据将会被验证。
Application_AuthorizeRequest:当安全模块确认一个用户能够访问资源以后,该事件被触发。
Application_ResolveRequestCache:在 ASP.NET 页面框架完成一个受权请求时,该事件被触发。它容许缓存模块从缓存中为请求提供服务,从而绕过事件处理程序的执行。
Application_AcquireRequestState:在 ASP.NET 页面框架获得与当前请求相关的当前状态(Session 状态)时,该事件被触发。
Application_PreRequestHandlerExecute:在 ASP.NET 页面框架开始执行诸如页面或 Web 服务之类的事件处理程序以前,该事件被触发。
Application_PreSendRequestHeaders:在 ASP.NET 页面框架发送 HTTP 头给请求客户(浏览器)时,该事件被触发。
Application_PreSendRequestContent:在 ASP.NET 页面框架发送内容给请求客户(浏览器)时,该事件被触发。
<<执行代码>>
Application_PostRequestHandlerExecute:在 ASP.NET 页面框架结束执行一个事件处理程序时,该事件被触发。
Application_ReleaseRequestState:在 ASP.NET 页面框架执行完全部的事件处理程序时,该事件被触发。这将致使全部的状态模块保存它们当前的状态数据。 Application_UpdateRequestCache:在 ASP.NET 页面框架完成事件处理程序的执行时,该事件被触发,从而使缓存模块存储响应数据,以供响应后续的请求时使用。
Application_EndRequest:针对应用程序请求的最后一个事件。
httpModule事件同Global.asax中的事件相对应,对应关系以下表:
HttpModule | Global.asax |
---|---|
BeginRequest | Application_BeginRequest |
AuthenticateRequest | Application_AuthenticateRequest |
EndRequest | Application_EndRequest |
既然global.asax中有的事件httpmodule也大部分都有那咱们使用哪一个比较好呢? | |
①、从范围来说,Global.asax要包括在HttpModule,由于在整个请求的过程当中,HttpModule只是从用户发出请求的时候才触发相关,好比服务器启动后要作的事情,它就不能触发,当整个请求都交给httphandler后,那么它就不起做用了,直到httphandler处理完后才可使用里面的方法,在httphandler处理请求的时候,session是无效的,因此Global.asax能够处理相关的session_end,session_start等事件,而httpMudel却不能,还有服务器中止的时候,须要触发的相关事件等等也只能在Global.asax中触发,还有有些功能,好比统计一个网站的在线人说或者访问量的时候也只能用Global.asax处理,可是共同的范围内还有不少事件能够一块儿处理的,好比防止sql注入,会在请求的时候过滤一些字符串,这个时候就可使用它们中的相关事件去处理 | |
②、从个数来讲,简单说一下HttpMudel能够多个 Global.asax只有一个,由于对于不一样的请求模块,能够建立不一样的HttpMudel,而全局应用程序Global.asax却只能有一个 | |
③、能够在应用程序的 Global.asax 文件中实现模块的许多功能,这使您能够响应应用程序事件。可是,模块相对于 Global.asax 文件具备以下优势:模块能够进行封装,所以能够在建立一次后在许多不一样的应用程序中使用。经过将它们添加到全局程序集缓存 (GAC) 并将它们注册到 Machine.config 文件中,能够跨应用程序从新使用它们。有关更多信息,可是,使用 Global.asax 文件有一个好处,那就是您能够将代码放在其余已注册的模块事件(如 Session_Start 和 Session_End 方法)中。此外,Global.asax 文件还容许您实例化可在整个应用程序中使用的全局对象。当您须要建立依赖应用程序事件的代码而且但愿在其余应用程序中重用模块时,或者不但愿将复杂代码放在 Global.asax 文件中时,应当使用模块。当您须要建立依赖应用程序事件的代码但不须要跨应用程序重用它时,或者须要订阅不可用于模块的事件(如 Session_Start)时,应当将代码放在Global.asax 文件中。 | |
总的来讲,HttpModule是处理请求的,可是不是全局的,而Global.asax则能够当作是全局的,好比,Application_Start,Session_Start等均可以在这里处理,而不能在HttpModule处理 |