HttpModule與HttpHandler詳解

jopen 12年前發布 | 1K 次閱讀

ASP.NET對請求處理的過程:
當請求一個*.aspx文件的時候,這個請求會被inetinfo.exe進程截獲,它判斷文件的后綴(aspx)之

后,將這個請求轉交給 ASPNET_ISAPI.dll,ASPNET_ISAPI.dll會通過http管道(Http PipeLine)將請求

發送給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-->HttpApplication Factory-

->HttpApplication-->HttpModule-->HttpHandler Factory-->HttpHandler-->HttpHandler.ProcessRequest()

ASP.NET請求處理過程是基于管道模型的,這個管道模型是由多個HttpModule和HttpHandler組成,

ASP.NET 把http請求依次傳遞給管道中各個HttpModule,最終被HttpHandler處理,處理完成后,再次

經過管道中的HTTP模塊,把結果返回給 客戶端。我們可以在每個HttpModule中都可以干預請求的

處理過程。

注意:在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應用程序的輸出緩沖是如何設置的。 
    上面這么多的事件,我們看起來可能會有些眼暈,但沒關系,下面一步一步地看。 

    下面是事件的觸發順序:

    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(){}   
     public void Init(HttpApplication context)   
     {   
         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事件 編寫代碼。
            第一步:創建一個類庫ClassLibrary831。
            第二步:編寫一個類實現IHttpModule接口


class TestModule:IHttpModule   
{   
    public void Dispose()   
    {   
    }   
    public void Init(HttpApplication context)   
    {   
    }   
}    

            第三步:在Init事件中注冊PreRequestHandlerExecute事件,并實現事件處理方法
  class AuthenticModule:IHttpModule   
  {   
      public void Dispose(){}   
      public void Init(HttpApplication context)   
      {   
          context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);   
      }   
      void context_PreRequestHandlerExecute(object sender, EventArgs e)   
      {   
          HttpApplication ha = (HttpApplication)sender;   
          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模塊
<httpModules>   
 <add name="TestModule" type="ClassLibrary831.TestModule,ClassLibrary831"></add>   
</httpModules>   

       3、多模塊的操作 
            如果定義了多個HttpModule,在web.config文件中引入自定義HttpModule的順序就決定了多個

自定義HttpModule在處理一個HTTP請求的接管順序。

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的作用是對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)   
     {   
         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;   
         }   
     }   
 }  

  //生成驗證碼的Handler   
 class CodeHandler:IHttpHandler   
 {   
     public bool IsReusable   
     {   
         get   
         {   
             return true;   
         }   
     }   
     public void ProcessRequest(HttpContext context)   
     {   
         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接口是個空接口,它沒有抽象方法,只是一個標記

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!