本章将和大家分享ASP.NET中的管道处理模型。
所谓管道处理模型,其实就是后台如何处理一个Http请求,定义多个事件完成处理步骤,每个事件可以扩展动作(IHttpModule), 最后有个IHttpHandler完成请求的处理,这个过程就是管道处理模型。
还有一个全局的上下文环境HttpContext,无论参数、中间结果、最终结果,都保存在其中。
下面我们将结合部门源码(通过ILSpy反编译得到)进行讲解:
首先我们先来看下 请求到程序响应 的示例图:
从图中可以看出Http请求需要经过一系列的步骤才会进入到我们的ASP.NET入口System.Web.HttpRuntime.ProcessRequest(HttpWorkerRequest wr)。
接下来我们就从请求进入ASP.NET入口开始讲解:
我们通过反编译工具ILSpy找到ASP.NET的入口System.Web.HttpRuntime.ProcessRequest(HttpWorkerRequest wr):
// System.Web.HttpRuntime [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)] public static void ProcessRequest(HttpWorkerRequest wr) { if (wr == null) { throw new ArgumentNullException("wr"); } if (HttpRuntime.UseIntegratedPipeline) { throw new PlatformNotSupportedException(SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", new object[] { "HttpRuntime.ProcessRequest" })); } HttpRuntime.ProcessRequestNoDemand(wr); }
接着我们沿 HttpRuntime.ProcessRequestNoDemand(wr) 一直往里找:
会找到System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)方法,如下所示:
// System.Web.HttpRuntime private void ProcessRequestInternal(HttpWorkerRequest wr) { Interlocked.Increment(ref this._activeRequestCount); if (this._disposingHttpRuntime) { try { wr.SendStatus(503, "Server Too Busy"); wr.SendKnownResponseHeader(12, "text/html; charset=utf-8"); byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>"); wr.SendResponseFromMemory(bytes, bytes.Length); wr.FlushResponse(true); wr.EndOfRequest(); } finally { Interlocked.Decrement(ref this._activeRequestCount); } return; } HttpContext httpContext; try { httpContext = new HttpContext(wr, false); } catch { try { wr.SendStatus(400, "Bad Request"); wr.SendKnownResponseHeader(12, "text/html; charset=utf-8"); byte[] bytes2 = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>"); wr.SendResponseFromMemory(bytes2, bytes2.Length); wr.FlushResponse(true); wr.EndOfRequest(); return; } finally { Interlocked.Decrement(ref this._activeRequestCount); } } wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, httpContext); HostingEnvironment.IncrementBusyCount(); try { try { this.EnsureFirstRequestInit(httpContext); } catch { if (!httpContext.Request.IsDebuggingRequest) { throw; } } httpContext.Response.InitResponseWriter(); IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext); if (applicationInstance == null) { throw new HttpException(SR.GetString("Unable_create_app_object")); } if (EtwTrace.IsTraceEnabled(5, 1)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, httpContext.WorkerRequest, applicationInstance.GetType().FullName, "Start"); } if (applicationInstance is IHttpAsyncHandler) { IHttpAsyncHandler httpAsyncHandler = (IHttpAsyncHandler)applicationInstance; httpContext.AsyncAppHandler = httpAsyncHandler; httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext); } else { applicationInstance.ProcessRequest(httpContext); this.FinishRequest(httpContext.WorkerRequest, httpContext, null); } } catch (Exception e) { httpContext.Response.InitResponseWriter(); this.FinishRequest(wr, httpContext, e); } }
从源码可以看出首先它是使用HttpWorkerRequest打包出一个HttpContext,然后再使用HttpContext创建一个IHttpHandler实例,最后用这个IHttpHandler实例来处理请求。
接下来我们沿着 HttpApplicationFactory.GetApplicationInstance(httpContext) 往里找:
// System.Web.HttpApplicationFactory internal static IHttpHandler GetApplicationInstance(HttpContext context) { if (HttpApplicationFactory._customApplication != null) { return HttpApplicationFactory._customApplication; } if (context.Request.IsDebuggingRequest) { return new HttpDebugHandler(); } HttpApplicationFactory._theApplicationFactory.EnsureInited(); HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context); return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context); }
其中 HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context) 这句话就是用来启动我们的网站完成项目初始化的,它会去调用我们的Global.asax里面的Application_Start方法。
我们继续往 HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context) 里面找:
// System.Web.HttpApplicationFactory private HttpApplication GetNormalApplicationInstance(HttpContext context) { HttpApplication httpApplication = null; if (!this._freeList.TryTake(out httpApplication)) { httpApplication = (HttpApplication)HttpRuntime.CreateNonPublicInstance(this._theApplicationType); using (new ApplicationImpersonationContext()) { httpApplication.InitInternal(context, this._state, this._eventHandlerMethods); } } if (AppSettings.UseTaskFriendlySynchronizationContext) { httpApplication.ApplicationInstanceConsumersCounter = new CountdownTask(1); Task arg_8A_0 = httpApplication.ApplicationInstanceConsumersCounter.Task; Action<Task, object> arg_8A_1; if ((arg_8A_1 = HttpApplicationFactory.<>c.<>9__34_0) == null) { arg_8A_1 = (HttpApplicationFactory.<>c.<>9__34_0 = new Action<Task, object>(HttpApplicationFactory.<>c.<>9.<GetNormalApplicationInstance>b__34_0)); } arg_8A_0.ContinueWith(arg_8A_1, httpApplication, TaskContinuationOptions.ExecuteSynchronously); } return httpApplication; }
可以看到该方法就是为了得到一个HttpApplication的实例,但是它并不是简单的创建HttpApplication的实例,HttpApplication有可能是重用的(对象池--Stack--会重用)。
我们点击HttpApplication进去看下:
可以看到它是实现 IHttpHandler和IHttpAsyncHandler 接口的。
到这里我们大概知道,任何一个Http请求一定是有一个IHttpHandler来处理的,任何一个Http请求就是一个HttpApplication对象来处理。
我们知道处理请求的过程一般包括固定步骤,例如:权限认证/缓存处理/Session处理/Cookie处理/生成html/输出客户端等,
与此同时,千千万万的开发者,又有各种各样的扩展诉求,任何一个环节都有可能要扩展,该怎么设计?
这里用的是观察者模式,把固定的步骤直接写在Handler里面,在步骤前&后分别放一个事件, 然后开发者可以对事件注册动作,等着请求进来了,然后就可以按顺序执行一下。
HttpApplication里面定义了一系列的事件,最终会按一定的顺序去执行这些事件,我们可以通过反编译工具来看下这些事件的执行顺序。
通过反编译工具找到System.Web.HttpApplication.ProcessEventSubscriptions方法(处理事件订阅的方法):
// System.Web.HttpApplication private void ProcessEventSubscriptions(out RequestNotification requestNotifications, out RequestNotification postRequestNotifications) { requestNotifications = (RequestNotification)0; postRequestNotifications = (RequestNotification)0; if (this.HasEventSubscription(HttpApplication.EventBeginRequest)) { requestNotifications |= RequestNotification.BeginRequest; } if (this.HasEventSubscription(HttpApplication.EventAuthenticateRequest)) { requestNotifications |= RequestNotification.AuthenticateRequest; } if (this.HasEventSubscription(HttpApplication.EventPostAuthenticateRequest)) { postRequestNotifications |= RequestNotification.AuthenticateRequest; } if (this.HasEventSubscription(HttpApplication.EventAuthorizeRequest)) { requestNotifications |= RequestNotification.AuthorizeRequest; } if (this.HasEventSubscription(HttpApplication.EventPostAuthorizeRequest)) { postRequestNotifications |= RequestNotification.AuthorizeRequest; } if (this.HasEventSubscription(HttpApplication.EventResolveRequestCache)) { requestNotifications |= RequestNotification.ResolveRequestCache; } if (this.HasEventSubscription(HttpApplication.EventPostResolveRequestCache)) { postRequestNotifications |= RequestNotification.ResolveRequestCache; } if (this.HasEventSubscription(HttpApplication.EventMapRequestHandler)) { requestNotifications |= RequestNotification.MapRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventPostMapRequestHandler)) { postRequestNotifications |= RequestNotification.MapRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventAcquireRequestState)) { requestNotifications |= RequestNotification.AcquireRequestState; } if (this.HasEventSubscription(HttpApplication.EventPostAcquireRequestState)) { postRequestNotifications |= RequestNotification.AcquireRequestState; } if (this.HasEventSubscription(HttpApplication.EventPreRequestHandlerExecute)) { requestNotifications |= RequestNotification.PreExecuteRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventPostRequestHandlerExecute)) { postRequestNotifications |= RequestNotification.ExecuteRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventReleaseRequestState)) { requestNotifications |= RequestNotification.ReleaseRequestState; } if (this.HasEventSubscription(HttpApplication.EventPostReleaseRequestState)) { postRequestNotifications |= RequestNotification.ReleaseRequestState; } if (this.HasEventSubscription(HttpApplication.EventUpdateRequestCache)) { requestNotifications |= RequestNotification.UpdateRequestCache; } if (this.HasEventSubscription(HttpApplication.EventPostUpdateRequestCache)) { postRequestNotifications |= RequestNotification.UpdateRequestCache; } if (this.HasEventSubscription(HttpApplication.EventLogRequest)) { requestNotifications |= RequestNotification.LogRequest; } if (this.HasEventSubscription(HttpApplication.EventPostLogRequest)) { postRequestNotifications |= RequestNotification.LogRequest; } if (this.HasEventSubscription(HttpApplication.EventEndRequest)) { requestNotifications |= RequestNotification.EndRequest; } if (this.HasEventSubscription(HttpApplication.EventPreSendRequestHeaders)) { requestNotifications |= RequestNotification.SendResponse; } if (this.HasEventSubscription(HttpApplication.EventPreSendRequestContent)) { requestNotifications |= RequestNotification.SendResponse; } }
通过上面的源码,我们就能很清楚的看出各个事件的执行顺序了。
下面我们可以通过一张图来更直观的了解这些事件的执行顺序,如下所示:
而对HttpApplication里面的事件进行动作注册的,就叫IHttpModule,下面我们就来看下如何实现一个自定义HttpModule:
首先我们先来看下Demo的目录结构:
本Demo的Web项目为ASP.NET Web 应用程序(目标框架为.NET Framework 4.5) MVC项目。
其中Home控制器:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace AspNetPipeline.Controllers { /// <summary> /// 1 Http请求处理流程 /// 2 HttpApplication的事件 /// 3 HttpModule /// 4 Global事件 /// /// Runtime--运行时 /// Context--上下文 /// 任何一个Http请求一定是有一个IHttpHandler来处理的 ashx aspx.cs MvcHttpHandler /// 任何一个Http请求就是一个HttpApplication对象来处理 /// 然后处理过程固定包含:权限认证/缓存处理/Session处理/Cookie处理/生成html/输出客户端 /// 与此同时,千千万万的开发者,又有各种各样的扩展诉求,任何一个环节都有可能要扩展,该怎么设计? /// 这里用的是观察者模式,把固定的步骤直接写在Handler里面,在步骤前&后分别放一个事件, /// 然后开发者可以对事件注册动作,等着请求进来了,然后就可以按顺序执行一下 /// /// 对HttpApplication里面的事件进行动作注册的,就叫IHttpModule /// 自定义一个HttpModule--配置文件注册--然后任何一个请求都会执行Init里面注册给Application事件的动作 /// 学习完HttpModule,我们可以做点什么有用的扩展? /// 1 日志-性能监控-后台统计数据 /// 2 权限 /// 3 缓存 /// 4 页面加点东西 /// 5 请求过滤--黑名单 /// 6 MVC--就是一个Module扩展 /// /// 不适合的(不是全部请求的,就不太适合用module,因为有性能损耗) /// 1 跳转到不同界面--也不适合 /// 2 防盗链--针对一类的后缀来处理,而不是全部请求--判断--再防盗链 /// /// HttpModule里面发布一个事件CustomHttpModuleHandler,在Global.asax增加一个动作, /// MyCustomHttpModule_CustomHttpModuleHandler(配置文件module名称_module里面事件名称),请求响应时,该事件会执行 /// /// HttpModule是对HttpApplication的事件注册动作,而Global则是对HttpModule里面的事件注册动作 /// /// /// C:WindowsMicrosoft.NETFramework64v4.0.30319Configweb.config /// .NetFramework安装路径,是一个全局的配置,是当前电脑上任何一个网站的默认配置,不要去修改它 /// /// /// 1 HttpHandler及扩展,自定义后缀,图片防盗链等 /// 2 RoutingModule,IRouteHandler、IHttpHandler /// 3 MVC扩展Route,扩展HttpHandle /// /// 配置文件指定映射关系:后缀名与处理程序的关系(IHttpHandler---IHttpHandlerFactory) /// Http任何一个请求一定是由某一个具体的Handler来处理的,不管是成功还是失败 /// 以前写aspx,感觉请求访问的是物理地址,其实不然,请求的处理是框架设置的 /// /// 所谓管道处理模型,其实就是后台如何处理一个Http请求,定义多个事件完成处理步骤,每个事件可以扩展动作(HttpModule), /// 最后有个HttpHandler完成请求的处理,这个过程就是管道处理模型。 /// 还有一个全局的上下文环境HttpContext,无论参数、中间结果、最终结果,都保存在其中。 /// /// 自定义Handler处理,就是可以处理各种后缀请求,可以加入自己的逻辑 /// 如果没有--请求都到某个页面--传参数---返回图片 /// 防盗链---加水印---伪静态---RSS--robot--trace.axd /// /// MVC里面不是controller action?其实是由 MvcHandler来处理请求,期间完成对action调用的 /// 网站启动时---对RouteCollection进行配置 /// 把正则规则和RouteHandler(提供HttpHandler)绑定,放入RouteCollection, /// 请求来临时---用RouteCollection进行匹配 /// 所谓MVC框架,其实就是在Asp.Net管道上扩展的,在PostResolveCache事件扩展了UrlRoutingModule, /// 会在任何请求进来后,先进行路由匹配,如果匹配上了,就指定HttpHandler;没有匹配就还是走原始流程 /// /// 扩展自己的Route,写入RouteCollection,可以自定义规则完成路由 /// 扩展HttpHandle,就可以为所欲为,跳出MVC框架 /// </summary> public class HomeController : Controller { public ActionResult Index() { return View(); } } }
对应的 /Home/Index 视图:
@{ ViewBag.Title = "Home Page"; } <h2> This is Home/Index View </h2> <hr />
未进行HttpModule注册前我们先来访问下 /Home/Index ,运行结果如下所示:
下面我们自定义一个HttpModule如下所示:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace AspNetPipeline.Pipeline { /// <summary> /// 自定义HttpModule /// </summary> public class CustomHttpModule : IHttpModule { public event EventHandler CustomHttpModuleHandler; public void Dispose() { Console.WriteLine("This is CustomHttpModule.Dispose"); } /// <summary> /// 注册动作context /// </summary> /// <param name="context"></param> public void Init(HttpApplication context) { context.BeginRequest += (s, e) => { this.CustomHttpModuleHandler?.Invoke(context, null); }; //为每一个事件,都注册了一个动作,向客户端输出信息 context.AcquireRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "AcquireRequestState ")); context.AuthenticateRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "AuthenticateRequest ")); context.AuthorizeRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "AuthorizeRequest ")); context.BeginRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "BeginRequest ")); context.Disposed += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "Disposed ")); context.EndRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "EndRequest ")); context.Error += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "Error ")); context.LogRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "LogRequest ")); context.MapRequestHandler += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "MapRequestHandler ")); context.PostAcquireRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostAcquireRequestState ")); context.PostAuthenticateRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostAuthenticateRequest ")); context.PostAuthorizeRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostAuthorizeRequest ")); context.PostLogRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostLogRequest ")); context.PostMapRequestHandler += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostMapRequestHandler ")); context.PostReleaseRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostReleaseRequestState ")); context.PostRequestHandlerExecute += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostRequestHandlerExecute ")); context.PostResolveRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostResolveRequestCache ")); context.PostUpdateRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostUpdateRequestCache ")); context.PreRequestHandlerExecute += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PreRequestHandlerExecute ")); context.PreSendRequestContent += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PreSendRequestContent ")); context.PreSendRequestHeaders += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PreSendRequestHeaders ")); context.ReleaseRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "ReleaseRequestState ")); context.RequestCompleted += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "RequestCompleted ")); context.ResolveRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "ResolveRequestCache ")); context.UpdateRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "UpdateRequestCache ")); } } }
可以在IHttpModule.Init方法内部为HttpApplication事件注册动作。
然后我们需要在Web.config里面配置下这个HttpModule节点,如下所示:
<!--托管管道模式为集成时使用这个配置--> <system.webServer> <modules> <add name="MyCustomHttpModule" type="AspNetPipeline.Pipeline.CustomHttpModule,AspNetPipeline"/> </modules> </system.webServer>
其中type值为【类的完整名称 + 英文逗号 + 项目名称】。
此处,我们还在CustomHttpModule里面定义了一个CustomHttpModuleHandler事件,那么我们要在哪里给这个事件注册动作呢?
可以在Global.asax里面为CustomHttpModuleHandler事件绑定动作,如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace AspNetPipeline { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } /// <summary> /// 为HttpModule里面的事件注册动作 /// 配置文件module名称_module里面事件名称 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void MyCustomHttpModule_CustomHttpModuleHandler(object sender, EventArgs e) { HttpContext.Current.Response.Write("<h2>This is MvcApplication/MyCustomHttpModule_CustomHttpModuleHandler</h2>"); } } }
最后我们再来访问下 /Home/Index,看下运行结果:
其中“This is Home/Index View”这句话就是由某一个具体的IHttpHandler处理器对象来处理的。
PS:
1、对HttpApplication里面的事件进行动作注册的,就叫IHttpModule。
2、自定义一个HttpModule--配置文件注册--然后任何一个请求都会执行Init里面注册给HttpApplication事件的动作。
3、HttpModule里面发布一个事件CustomHttpModuleHandler,在Global.asax增加一个动作, MyCustomHttpModule_CustomHttpModuleHandler(配置文件module名称_module里面事件名称),请求响应时,该事件会被执行。
4、HttpModule是对HttpApplication里面的事件注册动作,而Global则是对HttpModule里面的事件注册动作。
介绍到这里,我们知道Http的任何一个请求最终一定是由某一个具体的HttpHandler来处理的,不管是成功还是失败。
竟然如此,那我们能不能自定义一个HttpHandler来处理一些特殊的请求呢?答案:可以的。
例如:我们想要实现某个特定后缀(如.log后缀)的所有请求都指派给我们自定义的HttpHandler来处理,那这个要如何实现呢?
下面我们就带大家来实现这一想法,先来看下示例所涉及到的代码的目录结构:
自定义HttpHandler:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; namespace AspNetPipeline.Pipeline { /// <summary> /// 自定义HttpHandler /// /// 我们可以从请求级出发,避开默认机制,动态响应 .log(自定义)后缀的请求 /// </summary> public class CustomHttpHandler : IHttpHandler { public bool IsReusable => true; /// <summary> /// 处理请求 /// </summary> /// <param name="context"></param> public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/html"; context.Response.WriteFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web.config")); } } }
在Web.config里面进行配置说明:
<!--托管管道模式为集成时使用这个配置--> <system.webServer> <!--可以给自己留个后门,比如读个日志文件啥的--> <handlers> <!--这句话的意思就是.log后缀的所有请求就指派给我们的CustomHttpHandler来处理--> <add name="ReadLog" verb="*" path="*.log" type="AspNetPipeline.Pipeline.CustomHttpHandler,AspNetPipeline"/> </handlers> <modules> <add name="MyCustomHttpModule" type="AspNetPipeline.Pipeline.CustomHttpModule,AspNetPipeline"/> </modules> </system.webServer>
此时我们去访问一下 /log.log 会发现报错了,如下所示:
这是因为此时它被MVC的路由匹配了,所以无法找到资源。
我们需要到MVC路由配置那边把它忽略掉:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace AspNetPipeline { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { //忽略路由 正则表达式 {resource}表示变量 a.axd/xxxx resource=a pathInfo=xxxx //.axd是历史原因,最开始都是WebForm,请求都是.aspx后缀,IIS根据后缀转发请求; //MVC出现了,没有后缀,IIS6以及更早版本,打了个补丁,把MVC的请求加上个.axd的后缀,然后这种都转发到网站 //新版本的IIS已经不需要了,遇到了就直接忽略,还是走原始流程 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //该行框架自带的 //.log后缀的请求忽略掉,不走MVC流程,而是用我们自定义的CustomHttpHandler处理器来处理 routes.IgnoreRoute("{resource}.log/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
最后我们再来访问下 /log.log 运行结果如下所示:
可以发现此时访问正常了,我们右键查看网页源代码,会发现输出了我们想要的东西,如下所示:
之前我们在访问 .aspx 页面时可能有个错觉,感觉就是访问物理路径,然而从上面这个例子可以看出这是不对的。
它应该是由我们的配置文件来指定映射关系:后缀名与处理程序的关系(IHttpHandler---IHttpHandlerFactory) 。
自定义HttpHandler处理,就是可以处理各种后缀请求,可以加入自己的逻辑。
为了加深印象,下面我们就再举个防盗链的例子:
防盗链HttpHandler:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace AspNetPipeline.Pipeline { /// <summary> /// 防盗链HttpHandler /// </summary> public class ImageHttpHandler : IHttpHandler { public bool IsReusable => true; public void ProcessRequest(HttpContext context) { // 如果UrlReferrer为空,大部分都是爬虫,则显示一张默认的禁止盗链的图片 if (context.Request.UrlReferrer == null || context.Request.UrlReferrer.Host == null) { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/Content/Image/Forbidden.jpg"); } else { // 如果UrlReferrer中不包含自己站点主机域名,则显示一张默认的禁止盗链的图片 if (context.Request.UrlReferrer.Host.Contains("localhost")) { // 获取文件服务器端物理路径 string fileName = context.Server.MapPath(context.Request.FilePath); context.Response.ContentType = "image/JPEG"; context.Response.WriteFile(fileName); } else { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/Content/Image/Forbidden.jpg"); } } } } }
在Web.config里面进行配置:
<!--托管管道模式为集成时使用这个配置--> <system.webServer> <!--可以给自己留个后门,比如读个日志文件啥的--> <handlers> <!--这句话的意思就是.log后缀的所有请求就指派给我们的CustomHttpHandler来处理--> <add name="ReadLog" verb="*" path="*.log" type="AspNetPipeline.Pipeline.CustomHttpHandler,AspNetPipeline"/> <!--防盗链处理--> <add name="gif" path="*.gif" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" /> <add name="png" path="*.png" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" /> <add name="jpg" path="*.jpg" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" /> <add name="jpeg" path="*.jpeg" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" /> </handlers> <modules> <!--自定义HttpModule--> <!--<add name="MyCustomHttpModule" type="AspNetPipeline.Pipeline.CustomHttpModule,AspNetPipeline"/>--> </modules> </system.webServer>
PS:此处需要将自定义的CustomHttpModule这个配置节点给注释掉,因为在CustomHttpModule中注册的动作有向客户端输出字符串,这会导致图片输出异常。
图片存放路径如下所示:
访问 /content/image/scenery.jpg 运行结果如下所示:
可以发现此时返回的并不是我们访问的真实图片,而是防止盗链的图片。
至此本文就全部介绍完了,如果觉得对您有所启发请记得点个赞哦!!!
Demo源码:
链接:https://pan.baidu.com/s/1Rb4uq0yB_iB3VsonwiCFKw 提取码:68r6
此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/15201368.html
版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!