HttpModule與HttpHandler的區別(一)

CodeAgriculture發表於2013-03-29

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--&gtinetinfo.exe--&gtASPNET_ISAPI.dll--&gtASPNET_WP.exe--&gtHttpRuntime--&gtHttpApplication Factory--&gtHttpApplication--&gtHttpModule--&gtHttpHandler Factory--&gtHttpHandler--&gtHttpHandler.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 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--&gt");
}
}

第四步:在Web.Conofig中註冊一下這個HttpModule模組


   
  

name:模組名稱,一般是類名

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

這樣在Web站點是新增該類庫的引用後,執行每個頁面,會發現其原始檔中都會加入“”這句話。同樣的方法你也可以在其中加入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模組


   
  

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配置檔案



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進行管理。
工廠的作用請見

http://hi.baidu.com/grayworm/blog/item/4a832160f8c9de46eaf8f8c1.html"
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)
{
}
 }  

配置檔案



   

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

HttpHandler使用會話

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

ASP.Net處理Http Request時,使用Pipeline(管道)方式,由各個HttpModule對請求進行處理,然後到達 HttpHandler,HttpHandler處理完之後,仍經過Pipeline中各個HttpModule的處理,最後將HTML傳送到客戶端瀏覽 器中。

生命週期中涉及到幾個非常重要的對 象:HttpHandler,HttpModule,IHttpHandlerFactory,他們的執行(順序)大致的執行過程是這樣的:client 端傳送頁面請求,被IIS的某個程式截獲,它根據申請的頁 面字尾(.aspx)不同,呼叫不同的頁面處理程式(.asp->asp.dll; .aspx->ISAPI.dll).而頁面處理程式在處理過程中,則要經歷HttpModule,HttpHandler的處理:前者 HttpModule用於頁面處理前和處理後的一些事件的處理,後者HttpHandler進行真正的頁面的處理。

如前所說,HttpModule會在頁面處理前和後對頁面進行處理,所以它不會影響真正的頁 面請求。通常用在給每個頁面的頭部或者尾部新增一些資訊(如版 權宣告)等.曾經見過一些免費的空間,我們的頁面上傳上去後,瀏覽的時候發現,在每個頁面的頭部和尾部多了很多小廣告....,如果理解了 HttpModule的原理,要做這個就不是很難了~

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28699126/viewspace-757435/,如需轉載,請註明出處,否則將追究法律責任。

相關文章