天天看點

HttpModule與HttpHandler詳解

ASP.NET對請求處理的過程:

當請求一個*.aspx檔案的時候,這個請求會被inetinfo.exe程序截獲,它判斷檔案的字尾(aspx)之後,将這個請求轉交給ASPNET_ISAPI.dll,ASPNET_ISAPI.dll會通過http管道(HttpPipeLine)将請求發送給ASPNET_WP.exe程序,在ASPNET_WP.exe程序中通過HttpRuntime來處理這個請求,處理完畢将結果傳回用戶端。

     inetinfo.exe程序:是www服務的程序,IIS服務和ASPNET_ISAPI.DLL都寄存在此程序中。

     ASPNET_ISAPI.DLL:是處理.aspx檔案的win32元件。其實IIS伺服器是隻能識别.html檔案的,當IIS伺服器發現被請求的檔案是.aspx檔案時,IIS伺服器将其交給aspnet_isapi.dll來處理。

     aspnet_wp.exe程序:ASP.NET架構程序,提供.net運作的托管環境,.net的CLR(公共語言運作時)就是寄存在此程序中。

ASP.NET Framework處理一個Http Request的流程:

   HttpRequest-->inetinfo.exe-->ASPNET_ISAPI.dll-->ASPNET_WP.exe-->HttpRuntime-->HttpApplicationFactory-->HttpApplication-->HttpModule-->HttpHandlerFactory-->HttpHandler-->HttpHandler.ProcessRequest()

ASP.NET請求處理過程是基于管道模型的,這個管道模型是由多個HttpModule和HttpHandler組成,ASP.NET把http請求依次傳遞給管道中各個HttpModule,最終被HttpHandler處理,處理完成後,再次經過管道中的HTTP子產品,把結果傳回給用戶端。我們可以在每個HttpModule中都可以幹預請求的處理過程。

HttpModule與HttpHandler詳解

注意:在http請求的處理過程中,隻能調用一個HttpHandler,但可以調用多個HttpModule。 

當請求到達HttpModule的時候,系統還沒有對這個請求真正處理,但是我們可以在這個請求傳遞到進行中心(HttpHandler)之前附加一些其它資訊,或者截獲的這個請求并作一些額外的工作,也或者終止請求等。在HttpHandler處理完請求之後,我們可以再在相應的HttpModule中把請求處理的結果進行再次加工傳回用戶端。

HttpModule

     HTTP子產品是實作了System.Web.IhttpModule接口的類。

     IHttpModule接口的聲明:

         public interface IHttpModule

         {

             void Init (HttpApplication context);

             void Dispose ();

         }

       Init 方法:系統初始化的時候自動調用,這個方法允許HTTP子產品向HttpApplication 對象中的事件注冊自己的事件處理程式。

         Dispose方法: 這個方法給予HTTP子產品在對象被**收集之前執行清理的機會。此方法一般無需編寫代碼。

     HTTP子產品可以向System.Web.HttpApplication對象注冊下面一系列事件:

         AcquireRequestState 當ASP.NET運作時準備好接收目前HTTP請求的對話狀态的時候引發這個事件。 

         AuthenticateRequest 當ASP.NET 運作時準備驗證使用者身份的時候引發這個事件。 

         AuthorizeRequest 當ASP.NET運作時準備授權使用者通路資源的時候引發這個事件。 

         BeginRequest 當ASP.NET運作時接收到新的HTTP請求的時候引發這個事件。 

         Disposed 當ASP.NET完成HTTP請求的處理過程時引發這個事件。 

         EndRequest 把響應内容發送到用戶端之前引發這個事件。 

         Error 在處理HTTP請求的過程中出現未處理異常的時候引發這個事件。 

         PostRequestHandlerExecute 在HTTP處理程式結束執行的時候引發這個事件。 

         PreRequestHandlerExecute 在ASP.NET開始執行HTTP請求的處理程式之前引發這個事件。在這個事件之後,ASP.NET 把該請求轉發給适當的HTTP處理程式。 

         PreSendRequestContent 在ASP.NET把響應内容發送到用戶端之前引發這個事件。這個事件允許我們在内容到達用戶端之前改變響應内容。我們可以使用這個事件給頁面輸出添加用于所有頁面的内容。例如通用菜單、頭資訊或腳資訊。 

         PreSendRequestHeaders 在ASP.NET把HTTP響應頭資訊發送給用戶端之前引發這個事件。在頭資訊到達用戶端之前,這個事件允許我們改變它的内容。我們可以使用這個事件在頭資訊中添加cookie和自定義資料。 

         ReleaseRequestState 當ASP.NET結束所搜有的請求處理程式執行的時候引發這個事件。 

         ResolveRequestCache 我們引發這個事件來決定是否可以使用從輸出緩沖傳回的内容來結束請求。這依賴于Web應用程式的輸出緩沖時怎樣設定的。 

         UpdateRequestCache 當ASP.NET完成了目前的HTTP請求的處理,并且輸出内容已經準備好添加給輸出緩沖的時候,引發這個事件。這依賴于Web應用程式的輸出緩沖是如何設定的。

     上面這麼多的事件,我們看起來可能會有些眼暈,但沒關系,下面一步一步地看。

     HttpModule生命周期示意圖

HttpModule與HttpHandler詳解

     下面是事件的觸發順序:

HttpModule與HttpHandler詳解

     BeginRequest和PreRequestHandlerExecute之間的事件是在伺服器執行HttpHandler處理之前觸發。

     PostRequestHandlerExecute和PreSendRequestContent之間的事件是在伺服器執行Handler處理之後觸發。

     下面我們看一下如何使用HttpModule來實作我們日常的應用:

         HttpModule通過在某些事件中注冊,把自己插入ASP.NET請求處理管道。當這些事件發生的時候,ASP.NET調用對相應的HTTP子產品,這樣該子產品就能處理請求了。

        1、向每個頁面動态添加一些備注或說明性的文字:

             有的網站每一個頁面都會彈出一個廣告或在每個頁面都以注釋形式(<!-- -->)加入網站的版權資訊。如果在每個頁面教編寫這樣的JS代碼的話,對于大一點的網站,這種JS代碼的編寫與維護可是一個很繁瑣枯燥的工作。

           有了HttpModule我們就可以很簡單地解決這個問題了。HttpModule是用戶端送出請求到用戶端接收到伺服器響應之間的一段必經之路。我們完全可以在伺服器處理完請求之後,并在向用戶端發送響應文本之前這段時機,把這段注釋文字添加到頁面文本之後。這樣,每一個頁面請求都會被附加上這段注釋文字。

             這段代碼究竟該在哪個事件裡實作呢? PostRequestHandlerExecute和PreSendRequestContent之間的任何一個事件都可以,但我比較喜歡在EndRequest事件裡編寫代碼。

             第一步:建立一個類庫ClassLibrary831。

             第二步:編寫一個類實作IHttpModule接口

               class TestModule:IHttpModule

                 {

                     public void Dispose()

                     {

                     }

                     public void Init(HttpApplication context)

                 } 

             第三步:在Init事件中注冊EndRequest事件,并實作事件處理方法

                class TestModule:IHttpModule

                     public void Dispose(){}

                         context.EndRequest += new EventHandler(context_EndRequest);

                     void context_EndRequest(object sender, EventArgs e)

                         HttpApplication ha = (HttpApplication)sender;

                         ha.Response.Write("<!--這是每個頁面都會動态生成的文字。--grayworm-->");

             第四步:在Web.Conofig中注冊一下這個HttpModule子產品

          <httpModules>

          <add name="TestModule" type="ClassLibrary831.TestModule,ClassLibrary831"></add>

           </httpModules> 

           name:子產品名稱,一般是類名

           type:有兩部分組成,前半部分是命名空間和類名組成的全名,後半部分是程式集名稱,如果類是直接放在App_Code檔案夾中,那程式名稱是App_Code。

                 這樣在Web站點是添加該類庫的引用後,運作每個頁面,會發現其源檔案中都會加入“<!--這是每個頁面都會動态生成的文字。--grayworm-->”這句話。同樣的方法你也可以在其中加入JS代碼。

        2、身份檢查

             大家在作登入時,登入成功後,一般要把使用者名放在Session中儲存,在其它每一個頁面的Page_Load事件中都檢查Session中是否存在使用者名,如果不存在就說明使用者未登入,就不讓其通路其中的内容。

             在比較大的程式中,這種做法實在是太笨拙,因為你幾乎要在每一個頁面中都加入檢測Session的代碼,導緻難以開發和維護。下面我們看看如何使用HttpModule來減少我們的工作量

           由于在這裡我們要用到Session中的内容,我們隻能在AcquireRequestState和PreRequestHandlerExecute事件中編寫代碼,因為在HttpModule中隻有這兩事件中可以通路Session。這裡我們選擇PreRequestHandlerExecute事件編寫代碼。

                 class TestModule:IHttpModule

             第三步:在Init事件中注冊PreRequestHandlerExecute事件,并實作事件處理方法

                class AuthenticModule:IHttpModule

                         context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);

                     void context_PreRequestHandlerExecute(object sender, EventArgs e)

                         string path = ha.Context.Request.Url.ToString();

                         int n = path.ToLower().IndexOf("Login.aspx"); 

                         if (n == -1) //是否是登入頁面,不是登入頁面的話則進入{}

                         {

                             if (ha.Context.Session["user"] == null) //是否Session中有使用者名,若是空的話,轉向登入頁。

                             {

                                 ha.Context.Response.Redirect("Login.aspx?source=" + path);

                             }

                         }

             第四步:在Login.aspx頁面的“登入”按鈕中加入下面代碼

                 protected void Button1_Click(object sender, EventArgs e)

                     if(true)     //判斷使用者名密碼是否正确

                     { 

                         if (Request.QueryString["source"] != null)

                             string s = Request.QueryString["source"].ToLower().ToString();    //取出從哪個頁面轉來的

                             Session["user"] = txtUID.Text;

                             Response.Redirect(s); //轉到使用者想去的頁面

                         else

                             Response.Redirect("main.aspx");     //預設轉向main.aspx

                     } 

             第五步:在Web.Conofig中注冊一下這個HttpModule子產品

            <add name="TestModule" type="ClassLibrary831.TestModule,ClassLibrary831"></add>

        3、多子產品的操作 

             如果定義了多個HttpModule,在web.config檔案中引入自定義HttpModule的順序就決定了多個自定義HttpModule在處理一個HTTP請求的接管順序。

HttpModule與HttpHandler詳解

HttpHandler

     HttpHandler是HTTP請求的進行中心,真正地對用戶端請求的伺服器頁面做出編譯和執行,并将處理過後的資訊附加在HTTP請求資訊流中再次傳回到HttpModule中。

     HttpHandler與HttpModule不同,一旦定義了自己的HttpHandler類,那麼它對系統的HttpHandler的關系将是“覆寫”關系。

     IHttpHandler接口聲明

    public interface IHttpHandler

     {

         bool IsReusable { get; }

         public void ProcessRequest(HttpContext context); //請求處理函數

     }

     示例:把硬碟上的圖檔以流的方式寫在頁面上

       class TestHandler : IHttpHandler

             public void ProcessRequest(HttpContext context)

             {

                 FileStream fs = new FileStream(context.Server.MapPath("worm.jpg"), FileMode.Open);

                 byte[] b = new byte[fs.Length];

                 fs.Read(b, 0, (int)fs.Length);

                 fs.Close();

                 context.Response.OutputStream.Write(b, 0, b.Length);

             }

             public bool IsReusable

                 get

                     return true;

                 }

         Web.Config配置檔案

      <httpHandlers>

        <add verb="*" path="*" type="ClassLibrary831.TestHandler,ClassLibrary831"></add>

       </httpHandlers> 

            Verb屬性:指定了處理程式支援的HTTP動作。*-支援所有的HTTP動作;“GET”-支援Get操作;“POST”-支援Post操作;“GET, POST”-支援兩種操作。 

Path屬性:指定了需要調用處理程式的路徑和檔案名(可以包含通配符)。“*”、“*.aspx”、“showImage.aspx”、“test1.aspx,test2.aspx”

Type屬性:用名字空間、類名稱和程式集名稱的組合形式指定處理程式或處理程式工廠的實際類型。ASP.NET運作時首先搜尋bin目錄中的DLL,接着在GAC中搜尋。 

       這樣程式運作的效果是該網站的任何一個頁面都會顯示worm.jpg圖檔。如何隻讓一個頁面(default21.aspx)執行HttpHandler中的ProcessRequest方法呢?最簡單的辦法是在Web.Config檔案中把path配置資訊設為default21.aspx。

         根據這個例子大家可以考慮一下如何編寫“驗證碼”了。

IHttpHandler工廠

     IHttpHandlerFactory接口的聲明:

         public interface IHttpHandlerFactory

             IHttpHandler GetHandler (HttpContext context,string requestType,string url,string pathTranslated);

             void ReleaseHandler (IHttpHandler handler);

        GetHandler傳回實作IHttpHandler接口的類的執行個體,ReleaseHandler使工廠可以重用現有的處理程式執行個體。 

     示例:兩個用IHttpHandlerFactory來實作對不同HttpHandler的調用。

     有兩個HttpHandler:将圖檔顯示在頁面上的HttpHandler和生成驗證碼的Handler

         //将圖檔顯示在頁面上的Handler

         class TestHandler : IHttpHandler

             public void ProcessRequest(HttpContext context)

         //生成驗證碼的Handler 

         class CodeHandler:IHttpHandler

                 Image b = new Bitmap(50,20);

                 Graphics g = Graphics.FromImage(b);

                 SolidBrush sb = new SolidBrush(Color.White);

                 Font f = new Font("宋體", 12);

                 string str = "";

                 Random r = new Random();

                 for (int i = 0; i < 4; i++)

                     str += r.Next(10);

                 g.DrawString(str,f,sb,0,0);

                 b.Save(context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);

         } 

          IHttpHandler工廠

          class TestHandlerFactory : IHttpHandlerFactory

          {

             public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)

                 string fname = url.Substring(url.IndexOf('/') + 1);

                 while (fname.IndexOf('/') != -1)

                     fname = fname.Substring(fname.IndexOf('/') + 1);

                 string cname = fname.Substring(0, fname.IndexOf('.'));

                 string className ="";

                 className = "ClassLibrary831.CodeHandler";

                 object h = null;

                 try

                     //h = new TestHandler();

                     h = Activator.CreateInstance(Type.GetType(className));

                 catch (Exception e)

                     throw new HttpException("工廠不能為類型" + cname + "建立執行個體。", e);

                 return (IHttpHandler)h;

             public void ReleaseHandler(IHttpHandler handler)

          }(車延祿)

         配置檔案

     <httpHandlers>

    <add verb="*" path="default21.aspx,default22.aspx"type="ClassLibrary831.TestHandlerFactory,ClassLibrary831"></add>

    </httpHandlers>

    這樣TestHandlerFactory就會根據請求的不同頁面執行不同的HttpHandler處理程式了。

HttpHandler使用會話

     如果要在處理程式中使用Session,那必須把該HttpHandler實作IRequiresSessionState接口,,IRequiresSessionState接口是個空接口,它沒有抽象方法,隻是一個标記。此處就不作例子驗證了。

本文轉自lidup 51CTO部落格,原文連結:http://blog.51cto.com/lidup/139695,如需轉載請自行聯系原作者

繼續閱讀