本章將和大家分享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:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.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
版權聲明:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!