前言:做爲一個開發人員,咱們看過不少的關於開發的書,可是都是教咱們"知其然",並無教咱們"知其因此然",咱們開發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處理 |