在之前的ASP.NET是如何在IIS下工作的這篇文章中介紹了ASP.NET與IIS配合工作的機制,在http請求經過一系列處理后,最后到達ASP.NET管道中,這時,就是Http Modules和HttpHandler出場的時候了。
再來擺出管道工作時序圖來一看:
HttpModule
HttpModule是類似於過濾器的作用,可以沒有,也可以有任意個,每一個都可以訂閱管道事件中的任意個事件,在每個訂閱的事件中可自定義功能實現。
HttpModule是實現IHttpModule接口的類。接口如下:
public interface IHttpModule { // 摘要: // 處置由實現 System.Web.IHttpModule 的模塊使用的資源(內存除外)。 void Dispose(); // // 摘要: // 初始化模塊,並使其為處理請求做好准備。 // // 參數: // context: // 一個 System.Web.HttpApplication,它提供對 ASP.NET 應用程序內所有應用程序對象的公用的方法、屬性和事件的訪問 void Init(HttpApplication context); }
下面實現一個HttpModule,並訂閱管道中的一系列事件,訂閱事件就是在Init方法中綁定EventHandler的過程:
代碼有點長,因為我把每一個事件都訂閱了,這樣一來可以清楚的看出哪些事件執行了,這些事件執行的先后順序是什么。代碼如下:
public class MyModule : IHttpModule { #region IHttpModule Members public void Dispose() { //此處放置清除代碼。 } public void Init(HttpApplication context) { // 下面是如何處理 LogRequest 事件並為其 // 提供自定義日志記錄實現的示例 context.LogRequest += new EventHandler(OnLogRequest); context.BeginRequest += new EventHandler(context_BeginRequest); context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest); context.AcquireRequestState += new EventHandler(context_AcquireRequestState); context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest); context.Disposed += new EventHandler(context_Disposed); context.Error += new EventHandler(context_Error); context.EndRequest += new EventHandler(context_EndRequest); context.MapRequestHandler += new EventHandler(context_MapRequestHandler); context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState); context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest); context.PostAuthorizeRequest += new EventHandler(context_PostAuthorizeRequest); context.PostLogRequest += new EventHandler(context_PostLogRequest); context.PostReleaseRequestState += new EventHandler(context_PostReleaseRequestState); context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute); context.PostResolveRequestCache += new EventHandler(context_PostResolveRequestCache); context.PostUpdateRequestCache += new EventHandler(context_PostUpdateRequestCache); context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState); context.RequestCompleted += new EventHandler(context_RequestCompleted); context.ResolveRequestCache += new EventHandler(context_ResolveRequestCache); context.UpdateRequestCache += new EventHandler(context_UpdateRequestCache); context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute); context.PreSendRequestContent += new EventHandler(context_PreSendRequestContent); context.PreSendRequestHeaders += new EventHandler(context_PreSendRequestHeaders); context.PostMapRequestHandler += new EventHandler(context_PostMapRequestHandler); } void context_Error(object sender, EventArgs e) { WriteLog("Error"); //HttpContext.Current.Response.Write("Error<br />"); } void context_UpdateRequestCache(object sender, EventArgs e) { WriteLog("UpdateRequestCache"); //HttpContext.Current.Response.Write("UpdateRequestCache<br />"); } void context_ResolveRequestCache(object sender, EventArgs e) { WriteLog("ResolveRequestCache"); // HttpContext.Current.Response.Write("ResolveRequestCache<br />"); } void context_RequestCompleted(object sender, EventArgs e) { WriteLog("RequestCompleted"); // HttpContext.Current.Response.Write("RequestCompleted<br />"); } void context_ReleaseRequestState(object sender, EventArgs e) { WriteLog("ReleaseRequestState"); //HttpContext.Current.Response.Write("ReleaseRequestState<br />"); } void context_PostUpdateRequestCache(object sender, EventArgs e) { WriteLog("PostUpdateRequestCache"); //HttpContext.Current.Response.Write("PostUpdateRequestCache<br />"); } void context_PostResolveRequestCache(object sender, EventArgs e) { WriteLog("PostResolveRequestCache"); //HttpContext.Current.Response.Write("PostResolveRequestCache<br />"); } void context_PostRequestHandlerExecute(object sender, EventArgs e) { WriteLog("PostRequestHandlerExecute"); //HttpContext.Current.Response.Write("PostRequestHandlerExecute<br />"); } void context_PostReleaseRequestState(object sender, EventArgs e) { WriteLog("PostReleaseRequestState"); //HttpContext.Current.Response.Write("PostReleaseRequestState<br />"); } void context_PostLogRequest(object sender, EventArgs e) { WriteLog("PostLogRequest"); //HttpContext.Current.Response.Write("PostLogRequest<br />"); } void context_PostAuthorizeRequest(object sender, EventArgs e) { WriteLog("PostAuthorizeRequest"); //HttpContext.Current.Response.Write("PostAuthorizeRequest<br />"); } void context_PostAuthenticateRequest(object sender, EventArgs e) { WriteLog("PostAuthenticateRequest"); //HttpContext.Current.Response.Write("PostAuthenticateRequest<br />"); } void context_PostAcquireRequestState(object sender, EventArgs e) { WriteLog("PostAcquireRequestState"); //HttpContext.Current.Response.Write("PostAcquireRequestState<br />"); } void context_MapRequestHandler(object sender, EventArgs e) { WriteLog("MapRequestHandler"); //HttpContext.Current.Response.Write("MapRequestHandler<br />"); } void context_Disposed(object sender, EventArgs e) { WriteLog("Disposed"); //HttpContext.Current.Response.Write("Disposed<br />"); } void context_AuthorizeRequest(object sender, EventArgs e) { WriteLog("AuthorizeRequest"); //HttpContext.Current.Response.Write("AuthorizeRequest<br />"); } void context_AcquireRequestState(object sender, EventArgs e) { WriteLog("AcquireRequestState"); //HttpContext.Current.Response.Write("AcquireRequestState<br />"); } void context_PreSendRequestHeaders(object sender, EventArgs e) { WriteLog("PreSendRequestHeaders"); //HttpContext.Current.Response.Write("PreSendRequestHeaders<br />"); } void context_PreSendRequestContent(object sender, EventArgs e) { WriteLog("PreSendRequestContent"); //HttpContext.Current.Response.Write("PreSendRequestContent<br />"); } void context_PreRequestHandlerExecute(object sender, EventArgs e) { WriteLog("PreRequestHandlerExecute"); //HttpContext.Current.Response.Write("PreRequestHandlerExecute<br />"); } void context_EndRequest(object sender, EventArgs e) { WriteLog("EndRequest"); //HttpContext.Current.Response.Write("EndRequest<br />"); } void context_BeginRequest(object sender, EventArgs e) { WriteLog("*******************************************************************************"); HttpApplication app = sender as HttpApplication; WriteLog(app.Context.Request.Path); WriteLog("BeginRequest"); //HttpContext.Current.Response.Write("BeginRequest<br />"); } void context_AuthenticateRequest(object sender, EventArgs e) { WriteLog("AuthenticateRequest"); //HttpContext.Current.Response.Write("AuthenticateRequest<br />"); } #endregion public void OnLogRequest(Object source, EventArgs e) { //可以在此處放置自定義日志記錄邏輯 WriteLog("OnLogRequest"); //HttpContext.Current.Response.Write("OnLogRequest<br />"); } public void context_PostMapRequestHandler(object sender, EventArgs e) { WriteLog("PostMapRequestHandler"); //HttpContext.Current.Response.Write("PostMapRequestHandler<br />"); } public void WriteLog(string message) { string path = @"d:\aspnet\httpmodule.txt"; StreamWriter writer = null; if (!File.Exists(path)) { writer = File.CreateText(path); } else { FileInfo info = new FileInfo(path); writer = info.AppendText(); } writer.WriteLine(message); writer.Flush(); writer.Close(); } }
訂閱的事件實現中,將事件名稱保存到我本地D盤的一個文本文件中。
代碼實現完畢了,下一步就是要代碼起作用了,很簡單,只需要在web.config中簡單配置就可以了。配置中注意IIS7集成模式和IIS7經典模式(包括IIS6)的區別,配置如下:
<!--IIS6或者IIS7經典模式--> <system.web> <httpModules> <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/> </httpModules> </system.web> <!--IIS7集成模式--> <system.webServer> <modules> <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/> </modules> </system.webServer>
如此一來,一個HttpModule及其配置工作就完成了,接下來,發布網站到IIS或者直接在VS中運行,隨便訪問項目中的一個文件(任何文件類型都可以),我的項目中有一個WebForm2.aspx的頁面,我在瀏覽器中訪問這個頁面,發現頁面是空白的,因為頁面中我什么都沒寫,上面的Module實現中,我把輸出全部放到本地D盤的一個文本文件中了,ok,打開那個文本文件。如圖:
我們看到輸出內容,第2行是訪問的頁面地址,下面依次為訂閱的事件輸出,我們清楚的看到了事件的執行順序。
BeginRequest #發出信號表示創建任何給定的新請求。 此事件始終被引發,並且始終是請求處理期間發生的第一個事件 AuthenticateRequest #發出信號表示配置的身份驗證機制已對當前請求進行了身份驗證。 訂閱 AuthenticateRequest 事件可確保在處理附加模塊或事件處理程序之前對請求進行身份驗證 PostAuthenticateRequest #預訂 PostAuthenticateRequest 事件的功能可以訪問由 PostAuthenticateRequest 處理的任何數據 AuthorizeRequest #發出信號表示 ASP.NET 已對當前請求進行了授權。 訂閱 AuthorizeRequest 事件可確保在處理附加的模塊或事件處理程序之前對請求進行身份驗證和授權 PostAuthorizeRequest #發出信號表示 ASP.NET 已對當前請求進行了授權。 訂閱 PostAuthorizeRequest 事件可確保在處理附加的模塊或處理程序之前對請求進行身份驗證和授權 ResolveRequestCache #引發這個事件來決定是否可以使用從輸出緩沖返回的內容來結束請求。這依賴於Web應用程序的輸出緩沖時怎樣設置的 PostResolveRequestCache #在 ASP.NET 跳過當前事件處理程序的執行並允許緩存模塊滿足來自緩存的請求時發生 MapRequestHandler #ASP.NET 基礎結構使用 MapRequestHandler 事件來確定用於當前請求的請求處理程序 PostMapRequestHandler #在 ASP.NET 已將當前請求映射到相應的事件處理程序時發生 AcquireRequestState #當 ASP.NET 獲取與當前請求關聯的當前狀態(如會話狀態)時發生 PostAcquireRequestState #預訂 AcquireRequestState 事件的功能可以訪問由 PostAcquireRequestState 處理的任何數據 PreRequestHandlerExecute #在ASP.NET開始執行HTTP請求的處理程序之前引發這個事件。在這個事件之后,ASP.NET 把該請求轉發給適當的HTTP處理程序 PostRequestHandlerExecute #在 ASP.NET 事件處理程序(例如,某頁或某個 XML Web service)執行完畢時發生 ReleaseRequestState #在 ASP.NET 執行完所有請求事件處理程序后發生。 該事件將使狀態模塊保存當前狀態數據 PostReleaseRequestState #在 ASP.NET 已完成所有請求事件處理程序的執行並且請求狀態數據已存儲時發生 UpdateRequestCache #當 ASP.NET 執行完事件處理程序以使緩存模塊存儲將用於從緩存為后續請求提供服務的響應時發生 PostUpdateRequestCache #在 ASP.NET 完成緩存模塊的更新並存儲了用於從緩存中為后續請求提供服務的響應后,發生此事件 OnLogRequest #恰好在 ASP.NET 為當前請求執行任何記錄之前發生,即使發生錯誤,也會引發 LogRequest 事件 PostLogRequest #在 ASP.NET 處理完 LogRequest 事件的所有事件處理程序后發生 EndRequest #在 ASP.NET 響應請求時作為 HTTP 執行管線鏈中的最后一個事件發生 PreSendRequestContent #恰好在 ASP.NET 向客戶端發送內容之前發生,可能發生多次 PreSendRequestHeaders #恰好在 ASP.NET 向客戶端發送 HTTP 標頭之前發生 RequestCompleted #在任何托管模塊和處理程序執行后,它使模塊清理資源
訪問一個頁面的過程中,依次觸發了23個事件,而HttpModule可訂閱的事件個數為25個,觀察發現,Error和Disposed這兩個事件沒有觸發。Error事件在發生錯誤的情況下執行,而Disposed事件,當我們關閉剛才打開的頁面,再到文本文件里查看,發現Disposed事件出現了,所以Disposed在會話結束后觸發。
由於HttpModule的個數可以有多個,我們可以按照上面的方式定義HttpModule實現類,然后再web.config中增加配置項,就可以實現多個HttpModule同時訂閱管道事件了。
介紹完HttpModule,那么HttpHandler又是什么呢,它又在什么什么時候執行呢?接下來看一下HttpHandler。
HttpHandler
HttpHandler是HTTP請求的處理中心,真正地對客戶端請求的服務器頁面做出編譯和執行,並將處理過后的信息附加在HTTP請求信息流中再次返回到HttpModule中。
HttpHandler與HttpModule不同,一旦定義了自己的HttpHandler類,那么它對系統的HttpHandler的關系將是“覆蓋”關系。
HttpHandler是實IHttpHandler接口的類,IHttpHandler接口定義如下:
public interface IHttpHandler { // 摘要: // 獲取一個值,該值指示其他請求是否可以使用 System.Web.IHttpHandler 實例。 // // 返回結果: // 如果 System.Web.IHttpHandler 實例可再次使用,則為 true;否則為 false。 bool IsReusable { get; } // 摘要: // 通過實現 System.Web.IHttpHandler 接口的自定義 HttpHandler 啟用 HTTP Web 請求的處理。 // // 參數: // context: // System.Web.HttpContext 對象,它提供對用於為 HTTP 請求提供服務的內部服務器對象(如 Request、Response、Session // 和 Server)的引用。 void ProcessRequest(HttpContext context); }
接口中只有一個屬性和一個方法,所以實現一個HttpHandler也很簡單,下面實現一個簡單的HttpHandler,代碼如下:
public class MyIISHandler : IHttpHandler { /// <summary> /// 您將需要在網站的 Web.config 文件中配置此處理程序 /// 並向 IIS 注冊它,然后才能使用它。有關詳細信息, /// 請參見下面的鏈接: http://go.microsoft.com/?linkid=8101007 /// </summary> #region IHttpHandler Members public bool IsReusable { // 如果無法為其他請求重用托管處理程序,則返回 false。 // 如果按請求保留某些狀態信息,則通常這將為 false。 get { return true; } } public void ProcessRequest(HttpContext context) { //在此處寫入您的處理程序實現。 WriteLog("請求一個asox頁面"); } #endregion }
上面我實現了一個很簡單的HttpHandler類,在ProcessRequest方法中,調用上面的HttpModule類中寫文本文件的方法,在文本文件中寫入“請求一個asox頁面”,沒錯,是一個asox頁面,我自己定義的文件格式,下面我會在web.config中添加配置項:
<!--IIS6或者IIS7經典模式--> <system.web> <httpHandlers> <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/> </httpHandlers> </system.web> <!--IIS7集成模式--> <system.webServer> <handlers> <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/> </handlers> </system.webServer>
配置項屬性的解釋:
path:指定了需要調用處理程序的路徑和文件名(可以包含通配符)。“*”、“*.aspx”、“booklist.aspx”、“test1.aspx,test2.aspx”、“*.asox”、“*.txt”。
verb:指定了處理程序支持的HTTP動作。*-支持所有的HTTP動作;“GET”-支持Get操作;“POST”-支持Post操作;“GET, POST”-支持兩種操作。
type:用名字空間、類名稱和程序集名稱的組合形式指定處理程序或處理程序工廠的實際類型。ASP.NET運行時首先搜索bin目錄中的DLL,接着在GAC中搜索。
接着,發布站點到IIS。打開IIS,找到當前站點的“處理程序映射”,會發現多了剛剛配置的HttpHandler,如圖:
沒錯,關於對*.asox這種類型的文件,就可以映射到上面創建的HttpHandler來進行處理,觀察其它條目發現,像*.aspx、*.ashx的處理程序是System.Web.UI.PageHandlerFactory和System.Web.UI.SimpleHandlerFactory這樣的工廠類型。沒錯,可以指定處理程序為一個HttpHandler,也可以指定為一個抽象工廠類型。先不說工廠類型的事兒,訪問一下網站中的asox頁面,看一下文本文件的記錄情況。
起作用了,在HttpModule輸出的一堆信息中,夾雜着HttpHandler的輸出,當然這僅限於訪問asox類型的頁面,因為我只對路徑為*.asox的文件格式做了設置,修改下配置文件,例如將path=”*.asox”改為path=”*.aspx”,那么ASP.NET對*.aspx頁面原有的解析機制將被我們設置的處理程序所覆蓋。
回到上面的輸出內容,我們觀察HttpHandler輸出內容所在的位置,位於PreRequestHandlerExecute和PostRequestHandlerExecute這兩個事件之間,這與HttpApplication類中的管道事件的創建過程有關。
前面說到了,處理處理程序可以指定為一個工廠類型,下面,我就創建一個工廠類型的處理程序。
這里所說的工廠類型的處理程序,就是實現了IHttpHandlerFactory接口的類,IHttpHandlerFactory接口定義如下:
public interface IHttpHandlerFactory { // 摘要: // 返回實現 System.Web.IHttpHandler 接口的類的實例。 // // 參數: // context: // System.Web.HttpContext 類的實例,它提供對用於為 HTTP 請求提供服務的內部服務器對象(如 Request、Response、Session // 和 Server)的引用。 // // requestType: // 客戶端使用的 HTTP 數據傳輸方法(GET 或 POST)。 // // url: // 所請求資源的 System.Web.HttpRequest.RawUrl。 // // pathTranslated: // 所請求資源的 System.Web.HttpRequest.PhysicalApplicationPath。 // // 返回結果: // 處理請求的新的 System.Web.IHttpHandler 對象。 IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated); // // 摘要: // 使工廠可以重用現有的處理程序實例。 // // 參數: // handler: // 要重用的 System.Web.IHttpHandler 對象。 void ReleaseHandler(IHttpHandler handler); }
同樣很簡單,也是只有兩個接口方法,下面是實現這個接口的工廠,代碼如下:
public class MyHttpHandlerFactory:IHttpHandlerFactory { public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) { //寫日志 WriteLog(string.Format("requestType:{0}|url:{1}|pathTranslated:{2}", requestType, url, pathTranslated)); //生成一個IHttpHandler Type t = typeof(MyIISHandler); IHttpHandler handler = Activator.CreateInstance(t) as IHttpHandler; return handler; } public void ReleaseHandler(IHttpHandler handler) { } }
方法中,返回了前面創建的那個HttpHander類,依然調用記錄文本文件的方法輸出內容,方便觀察執行的實際和具體內容。配置文件改改為這個工廠類。
<add name="mycustomFactoryHandler" path="*.asox" verb="*" type="fengzheng.MyHttpHandlerFactory,handler_modules"/>
配置完畢后,訪問網站中的asox頁面,打開文本文件,內容如下:
我們發現,工廠類中構造IHttpHandler接口的方法發生在HttpModule的MapRequestHandler之后,這同樣與HttpApplication類中構造管道事件有關。
說了這么多,那么,HttpModule和HttpHandler究竟能干什么呢?
HttpModule很常用的一個作用就是Url重寫,URLRewriter就是基於HttpModule實現的。
另外,有通過HttpHandler對圖片加水印,防止盜鏈的。
具體的可以參考這篇文章
部署網站注意事項:
網站采用.net 4.0集成模式部署,集成模式是一種統一的請求處理管道,它將ASP.NET請求管道與IIS核心管道組合在一起,這種模式能夠提供更好的性能,能夠實現配置和治理的模塊化,而且增加了使用托管代碼模塊擴展IIS時的靈活性。IIS經典模式與集成模式的區別
集成模式和經典模式的配置文件稍有不同,部署時需要注意針對不同的部署模式,修改配置文件。在vs2013中新建的web應用程序,默認的web.config內容如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 有關如何配置 ASP.NET 應用程序的詳細信息,請訪問 http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> </configuration>
- 按照經典模式部署,配置文件應該如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 有關如何配置 ASP.NET 應用程序的詳細信息,請訪問 http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> <httpModules> <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/> </httpModules> <httpHandlers> <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/> </httpHandlers> </system.web> </configuration>
經典模式經測試總是出現如下錯誤,500.21 - 模塊無法識別:
HTTP 錯誤 500.21 - Internal Server Error 處理程序“PageHandlerFactory-ISAPI-4.0_64bit”在其模塊列表中有一個錯誤模塊“IsapiModule”
至於錯誤原因:目前還不是很清楚。
- 按照集成模式部署,配置文件應該如下,將配置信息放到system.webServer節點之下:
<?xml version="1.0" encoding="utf-8"?> <!-- 有關如何配置 ASP.NET 應用程序的詳細信息,請訪問 http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> <system.webServer> <modules> <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/> </modules> <handlers> <add name="mycustomhandler" path="*" verb="*" type="fengzheng.MyIISHandler,handler_modules"/> </handlers> </system.webServer> </configuration>