ASP.NET三劍客 HttpApplication HttpModule HttpHandler 解析

來源:互聯網
上載者:User

標籤:項目   建立   管道   階段   接收   檔案   檔案服務   資料   命名空間   

我們都知道,ASP.Net運行時環境中處理請求是通過一系列對象來完成的,包含HttpApplication,HttpModule, HttpHandler。之所以將這三個對象稱之為ASP.NET三劍客是因為它們簡直不要太重要,完全是ASP.NET界的中流砥柱,責任擔當啊。瞭解它們之前我們得Crowdsourced Security Testing道ASP.NET管道模型。

ASP.NET管道模型

這裡以IIS6.0為例,它在背景工作處理序w3wp.exe中會利用aspnet_isapi.dll載入.NET運行時。IIS6.0引入了應用程式集區的概念,一個背景工作處理序對應著一個應用程式集區。一個應用程式集區可以承載一個或多個Web應用。如果HTTP.SYS(HTTP監聽器,是Windows TCP/IP網路子程式的一部分,用於持續監聽HTTP請求)接收的請求是對該Web應用的第一次訪問,在成功載入運行時後,IIS會通過AppDomainFactory為該Web應用建立一個應用程式定義域。也就是說一個應用程式集區中會有多個應用程式定義域,它們共用一個背景工作處理序資源,但是又不會互相牽連影響。

隨後一個特殊的運行時IsapiRuntime被載入,會接管該HTTP請求。IsapiRuntime首先會建立一個IsapiWorkerRequest對象來封裝當前的HTTP請求,隨後將此對象傳遞給ASP.NET運行時HttpRunTime。從此時起,HTTP請求正式進入了ASP.NET管道。

HttpRunTime會根據IsapiWorkerRequest對象建立用於表示當前HTTP請求的內容物件HttpContext。隨著HttpContext對象的建立,HttpRunTime會利用HttpApplicationFactory建立或擷取現有的HttpApplication對象。

HttpApplication負責處理當前的HTTP請求。在HttpApplication初始化過程中,ASP.NET會根據設定檔載入並初始化註冊的HttpModule對象。對於HttpApplication來說,在它處理HTTP請求的不同階段會觸發不同的事件,而HttpModule的意義在於通過註冊HttpApplication的相應事件,將所需的操作注入整個HTTP請求的處理流程。

最終完成對HTTP請求的處理在HttpHandler中,不同的資源類型對應著不同類型的HttpHandler

整體處理流程:

抽象之後的處理流程:

HttpApplication

HttpApplication是整個ASP.NET基礎架構的核心,它負責處理分發給它的HTTP請求。

提起HttpApplication就不得不說全域設定檔global.asax。global.asax檔案為每個Web應用程式提供了一個從HttpApplication派生的Global類。該類包含事件處理常式,如Application_Start。

每個Web應用程式都會有一個Global執行個體,作為應用程式的唯一入口。我們知道ASP.NET應用程式啟動時,ASP.NET運行時只調用一次Application_Start。這似乎意味著在我們的應用程式中只有一個Global對象執行個體,但是可不是只有一個HttpApplication對象執行個體。

ASP.NET運行時維護一個HttpApplication對象池。當第一個請求抵達時,ASP.NET會一次建立多個HttpApplication對象,並將其置於HttpApplication對象池中,然後選擇其中一個對象來處理該請求。當後續請求到達時,運行時會從池中擷取一個HttpApplication對象與請求進行配對。該對象與請求相關聯,並且只有該請求,直到請求處理完成。當請求完成後,HttpApplication對象不會被回收,而是會返回到池中,以便稍後將其拉出為其他請求提供服務。通過使用HttpApplication對象來處理到的請求,HttpApplication對象每次只能處理一個請求,這樣其成員才可以於儲存針對每個請求的資料。下面我們來瞭解一下HttpApplication的成員。

前面我們講到過,HttpApplication對象是由HttpRunTime根據當前HTTP請求的內容物件HttpContext建立或從池子中擷取的,並且在HttpApplication初始化過程中,ASP.NET會根據設定檔載入並初始化註冊的HttpModule對象。HttpApplication中的Context屬性(HttpContext(上下文)類的執行個體)和Modules屬性(影響當前應用程式的HttpModule模組集合)就是用於存放它們的。在後面的HttpModule中還會講到它們。

HttpApplication處理請求的整個生命週期是一個相對複雜的過程,為什麼稱之為複雜呢?因為HttpApplication類中存在大量的請求觸發的事件,在請求處理的不同階段會觸發相應的事件。

我們可以通過HttpModule註冊相應的事件,將處理邏輯注入到HttpApplication處理請求的某個階段。這裡需要注意的是,從BeginRequest開始的事件,並不是每個管道事件都會被觸發。因為在整個處理過程中,隨時可以調用Response.End()或者有未處理的異常發生而提前結束整個過程。所有事件中,只有EndRequest事件是肯定會觸發的,(部分Module的)BeginRequest有可能也不會被觸發。這個我們會在後面的HttpModule中提及。

HttpApplication類重要的Init方法和Dispose方法,這二個方法均可重載。它們的調用時機為:

Init方法在Application_Start之後調用,而Dispose在Application_End之前調用,另外Application_Start在整個ASP.NET應用的生命週期內只激發一次(比如IIS啟動或網站啟動時),類似的Application_End也只有當ASP.NET應用程式關閉時被調用(比如IIS停止或網站停止時)。

HttpModule

在前面我們講解了ASP.NET管道模型和HttpApplication對象(其中的管道事件)。現在我們一起來瞭解一下HttpModule。

我們都知道ASP.NET高度可擴充,那麼是什麼成就了ASP.NET的高度擴充性呢?HttpModule功不可沒。HttpModule在初始化的過程中,會將一些回調操作註冊到HttpApplication相應的事件中,在HttpApplication請求處理生命週期的某一個階段,相應的事件被觸發,通過HttpModule註冊的回調操作也會被執行。

所有的HttpModule都實現了IHttpModule介面,它和HttpApplication是直接打交道的。在其初始化方法Init()中接受了一個HttpApplication對象,這就讓事件註冊變得十分容易了。

我在瞭解了HttpModule之後,不禁發出一聲驚歎,這不就是面向切面(AOP)嘛!!!我們可以把HttpModule理解為HTTP請求攔截器,攔截到HTTP請求後,它能修改正在被處理的Context上下文,完事兒之後,再把控制權交還給管道,如果還有其它模組,則依次繼續處理,直到所有Modules集合(前面提到過,存在於HttpApplication)中的HttpModule都“爽”完為止(可憐的HTTP請求就這樣給各個HttpModule輪X了)。也正是這種類似於攔截器模式的HttpModule,配合HttpApplication管道事件給ASP.NET帶來了高度可擴充性。

與HttpHandler針對某一種請求檔案不同,HttpModule則是針對所有的請求檔案,映射給指定的處理常式對請求進行處理,而這些處理,可以發生在請求管線中的任何一個事件中。也就是說你訂閱哪個事件,這些處理就發生於那個事件中,處理過後再執行,你訂閱過的事件的下一個事件,當然你也可以終止所有事件直接運行最後一個事件,這就意味這他可以不給HttpHandler機會。

前面兩段我們提到,HttpModule針對所有請求,處理可以發生在請求管線中的任何一個事件中。而且Modules集合中的所有HttpModule都要依次執行請求處理。這自然而然地讓我們在使用強大的HttpModule時要十分注意效能問題,需要觸發哪些事件處理,不需要觸發哪些事件處理,要有嚴格的控制。要不會讓程式負重,得不償失。

ASP.NET中內建了很多HttpModule。我們開啟C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config檔案夾下的webconfig檔案,可以發現這樣一段配置:

這些都是ASP.NET中內建的HttpModule配置。至於為什麼要放在這裡,原因也很簡單。這裡的配置都是.NET Framework的預設和基礎的配置,如果要配置在每個項目的webconfig檔案中,勢必會讓項目的配置變得十分複雜,所以統一都放到了這裡進行配置。

至於中的節點中的HttpModule配置的作用,我們上面也提到過。前面我們講到過,在HttpApplication初始化過程中,ASP.NET會根據設定檔載入並初始化註冊的HttpModule對象。註冊的HttpModule對象初始化後,存放在了HttpApplication的Modules屬性之中。具體初始化哪些HttpModule對象,當然就是和這些配置相關啦。

雖然ASP.NET中內建了很多HttpModule,但是我們可以實現自訂HttpModule給予擴充滿足需要。下面我們自己來實現一下自訂HttpModule:

首先我們建立一個MVC5控制器DefaultController,然後在控制器中建立一個視圖Index。在頁面顯示Hello World。

接下來我們建立一個自訂HttpModule(MyModule):

namespace WebApplication{    public class MyModule : IHttpModule    {        public void Dispose()        {            throw new NotImplementedException();        }        public void Init(HttpApplication context)        {            context.BeginRequest += new EventHandler(BeginRequest);            context.EndRequest += new EventHandler(EndRequest);        }        void BeginRequest(object sender, EventArgs e)        {            ((HttpApplication)sender).Context.Response.Write("<h1>請求處理開始前進入我的Module</h1>");        }        void EndRequest(object sender, EventArgs e)        {            ((HttpApplication)sender).Context.Response.Write("<h1>請求處理結束後進入我的Module</h1>");        }    }}

我們在初始化方法Init中對HttpApplication的管道事件BeginRequest和EndRequest分別進行了註冊。註冊的事件會在響應中輸出不同的文字。

最後不要忘記了在webconfig檔案中進行配置,當然這個webconfig檔案指的是自己項目的webconfig。我們需要告知ASP.NET我們有哪些需要處理的HttpModule,否則打死它他也不會知道我們的自訂HttpModule。

這裡需要的注意的是,在IIS6和IIS7傳統模式中,我們需要這樣配置:

<system.web>    <httpModules>      <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>    </httpModules>      </system.web>

type="WebApplication.MyModule,WebApplication"中的WebApplication.MyModule

指的是WebApplication命名空間下的MyModule類,後面的WebApplication是所在程式集的名稱。

而在IIS7整合模式中,需要這樣進行配置:

<system.webServer>    <modules>      <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>        </modules> </system.webServer>

否則會報下面的錯誤:

一切準備完畢。啟動項目請求/Default/Index頁面:

可以發現,我們的自訂HttpModule發揮作用了。前面我們提到過,Modules集合(前面提到過,存在於HttpApplication)中的HttpModule在執行到相應的管道事件時都會觸發自己的註冊事件。我們來試一下。

我們再建立一個自訂HttpModule(YourModule):

namespace WebApplication{    public class YourModule : IHttpModule    {        public void Dispose()        {            throw new NotImplementedException();        }        public void Init(HttpApplication context)        {            context.BeginRequest += new EventHandler(BeginRequest);            context.EndRequest += new EventHandler(EndRequest);        }        void BeginRequest(object sender, EventArgs e)        {            ((HttpApplication)sender).Context.Response.Write("<h1>請求處理開始前進入你的Module</h1>");        }        void EndRequest(object sender, EventArgs e)         {             ((HttpApplication)sender).Context.Response.Write("<h1>請求處理結束後進入你的Module</h1>");         }        }}

然後配置webconfig告訴ASP.NET我們又建立一個自訂HttpModule,你一定要幫我執行啊。

<system.webServer>    <modules>      <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>      <add name="YourModule" type="WebApplication.YourModule,WebApplication"/>         </modules></system.webServer>

最後啟動項目請求/Default/Index頁面:

結果恰恰說明了:HttpModule會對請求依次進行處理,直到所有Modules集合(前面提到過,存在於HttpApplication)中的HttpModule都處理完為止

那麼HttpModule會對請求進行處理的順序是怎麼控制的呢?我們可以改變一下webconfig配置的順序。

<system.webServer>    <modules>      <add name="YourModule" type="WebApplication.YourModule,WebApplication"/>       <add name="MyModule" type="WebApplication.MyModule,WebApplication"/>    </modules></system.webServer>

也就是說HttpModule的處理順序,是根據配置的先後順序來的,不存在什麼優先順序之說

HttpHandler

與HttpModule針對所有的請求檔案不同,HttpHandler是針對某一類型的檔案,映射給指定的處理常式對請求進行出來。換一句話說就是,對請求真正的處理是在HttpHandler中進行的,前面的處理都是打輔助。但是並不是每一次請求HttpHandler都有機會接手的,輔助(HttpModule)也可以不給HttpHandler機會。

所有的HttpHandler都實現了IHttpHandler介面,其中的方法ProcessRequest提供了處理請求的實現。也就是說請求處理都是在這裡面玩的,前提是輔助(HttpModule)得給機會,一會我們也寫個例子玩一玩。

和HttpModule一樣,HttpHandler類型建立與請求路徑模式之間的映射關係,也需要通過設定檔。在C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config檔案夾下的webconfig檔案中,也可以找到ASP.NET內建的HttpHandler配置。

ASP.NET中預設的HttpHandler映射操作發生在HttpApplication的PostMapRequestHandler事件之前觸發,這種預設的映射就是通過配置。還有一種映射的方法,我們可以調用當前HttpContext的RemapHandler方法將一個HttpHandler對象映射到當前的HTTP請求。如果不曾調用RemapHandler方法或者傳入的參數是null,則進行預設的HttpHandler映射操作。需要注意的是,通過RemapHandler方法進行映射的目的就是為了直接跳過預設的映射操作,而預設的映射操作是在HttpApplication的PostMapRequestHandler事件之前觸發,所以在這之前調用RemapHandler方法才有意義。

public sealed class HttpContext : IServiceProvider, IPrincipalContainer{   public void RemapHandler(IHttpHandler handler);  }

下面我們自己寫以一個自訂HttpHandler玩一玩,我們有時候會有這麼一個需求,自己的圖片只希望在自己的網站被訪問到,在其他網站或瀏覽器直接開啟都不可以正常訪問。那麼HttpHandler就很適合這種情境的處理,我們以jpg格式的圖片為例。

首先建立自訂HttpHandler(JPGHandler):

namespace WebApplication{    public class JPGHandler : IHttpHandler    {        public bool IsReusable        {            get            {                return false;            }        }        public void ProcessRequest(HttpContext context)        {            context.Response.ContentType = "image/jpg";            // 如果UrlReferrer為空白,則顯示一張預設的404圖片              if (context.Request.UrlReferrer == null || context.Request.UrlReferrer.Host == null)            {                context.Response.WriteFile("/error.jpg");                return;            }            if(context.Request.UrlReferrer.Host.IndexOf("localhost") < 0)            {                context.Response.WriteFile("/error.jpg");                return;            }            // 擷取檔案伺服器端實體路徑              string fileName = context.Server.MapPath(context.Request.FilePath);            context.Response.WriteFile(fileName);        }    }}

然後我們在網站下面添加兩張圖片做測試,當圖片不可以正常顯示時預設展示error圖片:

測試搞起來,我們在瀏覽器中直接請求index.jpg資源。

效果不對啊,在瀏覽器中直接請求index.jpg資源應該是顯示error圖片啊。什麼原因呢?不要忘了我們需要告訴ASP.NET我們自訂了HttpHandler,咱們沒進行配置,ASP.NET當然不會知道。進行配置之後再來試試。

<system.webServer>    <handlers>      <add name="jpg" path="*.jpg" verb="*" type="WebApplication.JPGHandler, WebApplication" />    </handlers></system.webServer>

這次效果對了,是我們想要的。關於跨域圖片訪問我們就不做測試了,感興趣的話可以自己試一試。

前面我們提到了HttpHandler預設的映射方式是通過配置,那麼我們再來試一試非預設的方式,通過HttpContextd的RemapHandler方法。

這又到了輔助(HttpModule)來幫忙的時候了,因為需要在HttpModule註冊管道事件。前文提到在PostMapRequestHandler事件之前調用RemapHandler方法才有意義。BeginRequest事件在PostMapRequestHandler事件之前,我們就在BeginRequest事件中調用RemapHandler方法。

namespace WebApplication{    public class MyModule : IHttpModule    {        public void Dispose()        {            throw new NotImplementedException();        }        public void Init(HttpApplication context)        {            context.BeginRequest += new EventHandler(BeginRequest);                    }        void BeginRequest(object sender, EventArgs e)        {            ((HttpApplication)sender).Context.RemapHandler(new JPGHandler());        }    }}

然後我們需要在webconfig中配置MyModule,注釋掉JPGHandler。

最後啟動項目,訪問index.jpg資源,結果果然不出意外,和預設通過配置一樣,我們的自訂HttpHandler起到了效果。

我們再來試一下在PostMapRequestHandler事件之後調用RemapHandler方法,真的會沒有意義嗎?

我們將RemapHandler方法調用放到AcquireRequestState事件中,AcquireRequestState事件是PostMapRequestHandler事件後的第一個事件。

namespace WebApplication{    public class MyModule : IHttpModule    {        public void Dispose()        {            throw new NotImplementedException();        }        public void Init(HttpApplication context)        {            context.AcquireRequestState += new EventHandler(AcquireRequestState);        }         void AcquireRequestState(object sender, EventArgs e)        {            ((HttpApplication)sender).Context.RemapHandler(new JPGHandler());        }    }}

然後啟動項目,再訪問index.jpg資源。

我們發現ASP.NET架構中已經給我們做了限定,並沒有給我們任何犯錯的機會!那麼ASP.NET內部是怎麼實現調用順序限定的呢?我們可以通過ILSpy看一下源碼。

圈紅的部分,每當RemapHandler執行時,它會將當前方法所在事件(在ASP,NET管道模型中我們提到了隨著HttpContext對象的建立,HttpRunTime會利用HttpApplicationFactory建立或擷取現有的HttpApplication對象,HttpApplication對象包含著一個HttpContext屬性,所以是能做到這一點的)和一個枚舉(如,對管道事件按照順序進行了枚舉編碼)進行比較,如果大於或等於這個枚舉(PostMapRequestHandler事件),說明是在PostMapRequestHandler事件之後進行的映射,便會拋出異常。

總結

理解掌握了HttpApplication,HttpModule, HttpHandler這些並不能讓我們變得牛逼,但是ASP.NET 的管道模型和高可擴充性的實現方式卻對我們有著借鑒性的意義。再就是我們學習一定要自己動手體驗一下,不要相信任何權威,要只相信自己的雙手和自己的眼睛。希望大家看完這篇文章,腦子裡能時刻記住這樣一張圖就OK了。

因為本人能力有限,所以文中錯誤難免,希望大家指正和提出寶貴建議。

參考:《ASP.NET MVC 5 架構揭秘》



擼碼那些事
來源:http://songwenjie.cnblogs.com/
聲明:本文為博主學習感悟總結,水平有限,如果不當,歡迎指正。如果您認為還不錯,不妨點擊一下下方的【推薦】按鈕,謝謝支援。轉載與引用請註明出處。
公眾號:


ASP.NET三劍客 HttpApplication HttpModule HttpHandler 解析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.