ASP.NET中的HTTP模塊和處理程式-.NET教程,Asp.Net研發

在internet時代的開端,客戶端的需求很有限;.htm文檔就能夠滿足他們的需求。但是,隨着時間的流逝,客戶端需求的擴充超越了.htm文檔或靜態文檔所包含的功能。
  研發者需要擴充或擴展web服務器的功能。web服務器廠商設計了不同的解決方案,但是都遵循同一個主題“向web服務器插入某些組件”。任何的web服務器補充技術都允許研發者建立並插入組件以增強web服務器的功能。微軟公司提出了isapi(internet服務器api),網景公司提出了nsapi(網景服務器api)等等。

  isapi是一種重要的技術,他允許我們增強和isapi兼容的web服務器(iis就是一種和isapi兼容的web服務器)的能力。我們使用下面的組件達到這個目的:

  · isapi擴展

  · isapi過濾器

  isapi擴展是使用win32動態鏈接庫來實現的。您能夠把isapi擴展看作是個普通的應用程式。isapi擴展的處理目標是http請求。這意味着您必須調用他們才能激活他們。 您能夠認爲isapi過濾器僅僅就是個過濾器而已。客戶端每次向服務器發出請求的時候,請求要經過過濾器。客戶端無需在請求中指定過濾器,只需要簡單地把請求發送給web服務器,接着web服務器把請求傳遞給相關的過濾器。接下來過濾器可能修改請求,執行某些登錄操作等等。

  由於這些組件的複雜性,實現他們很困難。研發者不得不使用c/c++來研發這些組件,但是對於很多人來說,使用c/c++進行研發簡直就是痛苦的代名詞。

  那麼asp.net提供什麼東西來實現這些功能呢?asp.net提供的是httphandler(http處理程式)和httpmodule(http模塊)。

  在深入瞭解這些組件的周詳信息之前,瞭解一下http請求經過http模塊和http處理程式的時候的處理流程是有價值的。

  建立示例應用程式

  我建立了下面一些的c#項目以演示應用程式的不同組件:

  · newhandler (http處理程式)

  · webapp (演示http處理程式)

  · securitymodules (http模塊)

  · webapp2 (演示http模塊)

  這些應用程式的安裝步驟:

  · 解開attached zip文檔中的所以代碼。

  · 建立兩個虛擬目錄webapp和webapp2;把這兩個目錄指向webapp和webapp2應用程式的實際物理目錄。

  · 把newhandler項目中的newhandler.dll文檔複製到webapp應用程式的bin目錄。

  · 把securitymodules項目中的securitymodules.dll文檔複製到webapp2應用程式的bin目錄中。



  asp.net請求的處理過程

  asp.net請求處理過程是基於管道模型的,在模型中asp.net把http請求傳遞給管道中的任何模塊。每個模塊都接收http請求並有完全控制權限。模塊能夠用任何自認爲適合的方式來處理請求。一旦請求經過了任何http模塊,就最終被http處理程式處理。http處理程式對請求進行一些處理,並且結果將再次經過管道中的http模塊:


  請注意在http請求的處理過程中,只能調用一個http處理程式,然而能夠調用多個http模塊。

  http處理程式

  http處理程式是實現了system.web.ihttphandler接口的.net組件。任何實現了ihttphandler接口的類都能夠用於處理輸入的http請求。http處理程式和isapi擴展有些類似。http處理程式和isapi擴展的差別在於在url中能夠使用http處理程式的文檔名稱直接調用他們,和isapi擴展類似。

  http處理程式實現了下列方法:

方法名稱 描述
processrequest 這個方法實際上是http處理程式的核心。我們調用這個方法來處理http請求。
isreusable 我們調用這個屬性來決定http處理程式的實例是否能夠用於處理相同其他類型的請求。http處理程式能夠返回true或false來表明他們是否能夠重複使用。

  您能夠使用web.config或machine.config文檔把這些類映射到http請求上。映射完成以後,當接收到相應請求的時候asp.net會實例化http處理程式。我們將解釋如何在web.config和/或machine.config文檔中定義任何這些細節信息。

  asp.net還通過ihttphandlerfactory接口支持http處理程式的擴展。asp.net提供了把http請求路由到實現ihttphandlerfactory接口的類的對象上的能力。此外,asp.net還利用了factory設計模式。這種模式爲建立一組相關對象而不提供具體類的功能提供了接口。簡單的說,您能夠把用於建立依賴傳遞進來的參數建立的http處理程式對象的類看作是factory(工廠)。我們不用指定需要實例化的特定的http處理程式;http處理程式工廠處理這種事務。這樣做的長處在於假如未來實現ihttphandler接口的對象的實現方法發生了改變,只要接口仍然相同,客戶端就不會受到影響。

  下面是ihttphandlerfactory接口中的方法列表:

方法名稱 描述
gethandler 這個方法負責建立適當的處理程式並把他的指針返回到調用代碼(asp.net運行時)。這個方法返回的處理程式對象應該實現了ihttphandler接口。
releasehandler 這個方法負責在請求處理完成後釋放http處理程式。factory 實現決定了他的操作。factory 實現能夠是實際摧毀實例,也能夠把他放入緩衝池供以後使用。

  在配置文檔中註冊http處理程式和http處理程式工廠

  asp.net在下面的配置文檔中維護自己的配置信息:

  · machine.config

  · web.config

  machine.config文檔包含應用於電腦上安裝的任何web應用程式的配置配置信息。

  web.config文檔對於每個web應用程式來說是特定的。每個web應用程式都有自己的web.config文檔。web應用程式的任何子目錄也可能包含自己的web.config文檔;這使得他們能夠覆蓋父目錄的配置信息。
爲了給我們的web應用程式添加http處理程式,您能夠使用<httphandlers>和<add>節點。實際上,處理程式都帶有<add>節點,列舉在<httphandlers>和</httphandlers>節點之間。下面是添加http處理程式的一個普通的例子:

<httphandlers>
 <add verb="supported http verbs" path="path" type="namespace.classname, assemblyname" />
<httphandlers>

  在上面的xml中,

  · verb屬性指定了處理程式支持的http動作。假如某個處理程式支持任何的http動作,請使用“*”,否則使用逗號分隔的列表列出支持的動作。因此假如您的處理程式只支持http get和post,那麼verb屬性就應該是“get, post”。

  · path屬性指定了需要調用處理程式的路徑和文檔名(能夠包含通配符)。例如,假如您希望自己的處理程式只有在test.xyz文檔被請求的時候才被調用,那麼path屬性就包含“test.xyz”,假如您希望含有.xyz後綴的任何文檔都調用處理程式,path屬性應該包含“*.xyz”。

  · type屬性用名字空間、類名稱和部件名稱的組合形式指定處理程式或處理程式工廠的實際類型。asp.net運行時首先搜索應用程式的bin目錄中的部件dll,接着在全局部件緩衝(gac)中搜索。


  asp.net運行時對http處理程式的使用方式

  無論您是否相信,asp.net都使用http請求實現了大量的自己的功能。asp.net使用處理程式來處理.aspx、 .asmx、 .soap和其他asp.net文檔。

  下面是machine.config文檔中的一個片段:

<httphandlers>
 <add verb="*" path="trace.axd" type="system.web.handlers.tracehandler"/>
 <add verb="*" path="*.aspx" type="system.web.ui.pagehandlerfactory"/>
 <add verb="*" path="*.ashx" type="system.web.ui.simplehandlerfactory"/>
 <add verb="*" path="*.config" type="system.web.httpforbiddenhandler"/>
 <add verb="get,head" path="*" type="system.web.staticfilehandler"/>
 . . . . . .
 . . . . . .
</httphandlers>

  在上面的配置信息中您能夠看到對.aspx文檔的任何請求都由system.web.ui.pagehandlerfactory類來處理。和此類似,對.config文檔和其他文檔(他們不能被客戶端直接訪問)的任何請求都由system.web.httpforbiddenhandler類處理。您可能已猜到,當訪問這些文檔的時候,該類簡單地給客戶端返回一個錯誤信息。

  執行http處理程式

  現在您將看到如何實現一個http處理程式。那麼我們的新處理程式要做什麼任務呢?前面我提到,處理程式大多數用於給web服務器添加新功能;因此,我將建立一個處理程式來處理新的文檔類型——擴展名爲.15seconds的文檔。我們建立了這個處理程式並在我們的web應用程式的web.config文檔中註冊之後,任何對.15seconds文檔的請求都將由這個新處理程式來處理。

  您可能正在考慮這個處理程式的使用方法。假如您希望引入一種新的服務器腳本語言或動態服務器文檔(例如asp、aspx)該怎麼辦呢?您能夠爲他編寫一個自己的處理程式。類似地,假如您希望在iis上運行java小程式、jsp和其他一些服務器端java組件應該怎麼辦呢?一種方法是安裝某些isapi擴展(例如allaire或macromedia jrun)。您也能夠編寫自己的http處理程式。儘管這對於第三方廠商(例如allaire和macromedia)來說是很複雜的事務,但是他卻是個很有吸引力的選擇,因爲他們的http處理能夠能夠訪問asp.net運行時暴露的任何新功能。

  實現我們的http處理程式包含以下步驟:

  1.編寫一個實現ihttphandler接口的類。

  2. 在web.config或machine.config文檔中註冊這個處理程式。

  3.在internet服務管理器中把文檔擴展(.15seconds)映射到asp.net isapi擴展dll(aspnet_isapi.dll)上。

  第一步

  在visual studio.net中建立一個新的c#類庫項目,並把他命名爲“myhandler”。visual studio.net將自動地給項目添加一個叫做“class1.cs”的類。把他改名爲“newhandler”;在代碼窗口中打開這個類,並把類的名稱和構造函數的名稱改成“newhandler”。

  下面是newhandler類的代碼:

using system;
using system.web;

namespace myhandler
{
 public class newhandler : ihttphandler
 {
  public newhandler()
  {
   // todo: 此處添加構造邏輯
  }

  #region implementation of ihttphandler
  public void processrequest(system.web.httpcontext context)
  {
   httpresponse objresponse = context.response ;
   objresponse.write("<html><body><h1>hello 15seconds reader ") ;
   objresponse.write("</body></html>") ;
  }

  public bool isreusable
  {
   get
   {
    return true;
   }
  }
  #endregion
 }
}

  您在processrequest方法中能夠看到,該http處理程式通過system.web.httpcontext對象訪問了任何作爲參數傳遞給他的asp.net內部對象。實現processrequest方法只需要簡單地從context對象中提取httpresponse對象並把發送一些html給客戶端。類似地,isreusable返回true,表明這個處理程式能夠被重複用作處理其他的http請求。

  我們編譯上面的代碼並把他放到webapp虛擬目錄的bin目錄之中。

  第二步

  在web.config文檔中通過添加下面的文本來註冊這個處理程式:

<httphandlers>
 <add verb="*" path="*.15seconds" type="myhandler.newhandler,myhandler"/>
</httphandlers>

  第三步

  由於我們已建立了用於處理新擴展文檔的處理程式了,我們還需要把這個擴展名告訴iis並把他映射到asp.net。假如您不執行這個步驟而試圖訪問hello.15seconds文檔,iis將簡單地返回該文檔而不是把他傳遞給asp.net運行時。其結果是該http處理程式不會被調用。

  運行internet服務管理器,右鍵點擊默認web站點,選擇屬性,移動到home目錄選項頁,並點擊配置按鈕。應用程式配置對話框彈出來了。點擊添加按鈕並在可執行字段輸入aspnet_isapi.dll文檔路徑,在擴展字段輸入.15seconds。其他字段不用處理;該對話框如下所示:


  點擊確認按鈕關閉應用程式配置和默認web站點屬性對話框。

  現在我們運行internet explorer並輸入url:http://localhost/webapp/hello.15seconds,看到的頁面如下:



  http處理程式中的對話狀態

  維護對話狀態是web應用程式執行的最通常的事務。http處理程式也需要訪問這些對話狀態。但是http處理程式的默認配置是沒有激活對話狀態的。爲了讀取和/或寫入狀態數據,需要http處理程式實現下面的接口之一:

  · irequiressessionstate

  · ireadonlysessionstate.

  當http處理程式需要讀寫對話數據的時候,他必須實現irequiressessionstate接口。假如他只讀取對話數據,實現ireadonlysessionstate接口就能夠了。

  這兩個接口都是標記接口,並沒有包含任何方法。因此,假如您希望激活newhandler處理程式的對話狀態,要像下面的代碼相同聲明newhandler類:

public class newhandler : ihttphandler, irequiressessionstate

  http模塊

  http模塊是實現了system.web.ihttpmodule接口的.net組件。這些組件通過在某些事件中註冊自身,把自己插入asp.net請求處理管道。當這些事件發生的時候,asp.net調用對請求有興趣的http模塊,這樣該模塊就能處理請求了。

  http模塊實現了ihttpmodule接口的下面一些方法:

方法名稱 描述
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應用程式的輸出緩衝是如何配置的。

  除了這些事件之外,我們還能夠使用四個事件。我們能夠通過實現web應用程式的global.asax文檔中一些方法來使用這些事件。

  這些事件是:

  · application_onstart

  當第一個請求到達web應用程式的時候引發這個事件。

  · application_onend

  準備終止應用程式之前引發這個事件。

  · session_onstart

  用戶對話的第一個請求引發這個事件。

  · session_onend

  放棄對話或對話超期的時候引發這個事件。


  在配置文檔中註冊http模塊

  當我們建立了http模塊並把他複製到web應用程式的bin目錄或全局部件緩衝(global assembly cache)之後,接下來就應該在web.config或machine.config中註冊他了。

  我們能夠使用<httpmodules>和<add>節點把http模塊添加到web應用程式中。實際上模塊都使用<add>節點列舉在<httpmodules>和</httpmodules>節點之內了。

  因爲配置配置信息是能夠繼承的,所以子目錄從父目錄那兒繼承配置配置信息。其結果是,子目錄可能繼承了一些無需的http模塊(他們是父配置信息的一部分);因此,我們需要一種刪除這些無需的模塊的方法。我們能夠使用<remove>節點;假如我們希望刪除從應用程式繼承得到的任何http模塊,能夠使用<clear>節點。

  下面的代碼是添加http模塊的一個通用示例:

<httpmodules>
<add type="classname, assemblyname" name="modulename" />
<httpmodules>

  下面的代碼是從應用程式中刪除http模塊的一個通用示例:

<httpmodules>
<remove name="modulename" />
<httpmodules>

  在上面的xml中:

  · type屬性用類和部件名稱的形式指定了http模塊的實際類型。

  · name屬性指定了模塊的友好名稱。其他應用程式能夠使用這個名稱來識別http模塊。

  asp.net運行時如何使用http模塊

  asp.net運行時使用http模塊實現某些特別的功能。下面的片段來自於machine.config文檔,他顯示了asp.net運行時安裝的http模塊:

<httpmodules>
 <add name="outputcache" type="system.web.caching.outputcachemodule"/>
 <add name="session" type="system.web.sessionstate.sessionstatemodule"/>
 <add name="windowsauthentication"
type="system.web.security.windowsauthenticationmodule"/>
 <add name="formsauthentication"
type="system.web.security.formsauthenticationmodule"/>
 <add name="passportauthentication"
type="system.web.security.passportauthenticationmodule"/>
 <add name="urlauthorization"
type="system.web.security.urlauthorizationmodule"/>
 <add name="fileauthorization"
type="system.web.security.fileauthorizationmodule"/>
</httpmodules>

  asp.net使用上面一些http模塊來提供一些服務,例如身份驗證和授權、對話管理和輸出緩衝。由於這些模塊都註冊在machine.config文檔中。


  實現一個提供安全服務的http模塊

  現在我們實現一個http模塊,他爲我們的web應用程式提供安全服務。該http模塊基本上是提供一種定製的身份認證服務。他將接收http請求中的身份憑證,並確定該憑證是否有效。假如有效,和用戶相關的角色是什麼?通過user.identity對象,他把這些角色和訪問我們的web應用程式頁面的用戶的標識關聯起來。
下面是該http模塊的代碼:

using system;
using system.web;
using system.security.principal;

namespace securitymodules
{
 /// class1的總體描述。

 public class customauthenticationmodule : ihttpmodule
 {
  public customauthenticationmodule()
  {
  }
  public void init(httpapplication r_objapplication)
  {
   // 向application 對象註冊事件處理程式。
   r_objapplication.authenticaterequest +=
new eventhandler(this.authenticaterequest) ;
  }

  public void dispose()
  {
   // 此處空出,因爲我們無需做什麼操作。
  }

  private void authenticaterequest(object r_objsender,eventargs r_objeventargs)
  {
   // 鑑別用戶的憑證,並找出用戶角色。。
   1. httpapplication objapp = (httpapplication) r_objsender ;
   2. httpcontext objcontext = (httpcontext) objapp.context ;
   3. if ( (objapp.request["userid"] == null) ||
   4.  (objapp.request["password"] == null) )
   5.  {
   6.   objcontext.response.write("<h1>credentials not provided</h1>") ;
   7.   objcontext.response.end() ;
   8.  }

   9. string userid = "" ;
   10. userid = objapp.request["userid"].tostring() ;
   11. string password = "" ;
   12. password = objapp.request["password"].tostring() ;
 
   13. string[] strroles ;
   14. strroles = authenticateandgetroles(userid, password) ;
   15. if ((strroles == null) || (strroles.getlength(0) == 0))
   16. {
   17.  objcontext.response.write("<h1>we are sorry but we could not
find this user id and password in our database</h1>") ;
   18.  objapp.completerequest() ;
   19. }

   20. genericidentity objidentity = new genericidentity(userid,
"customauthentication") ;
   21. objcontext.user = new genericprincipal(objidentity, strroles) ;
  }

  private string[] authenticateandgetroles(string r_struserid,string r_strpassword)
  {
   string[] strroles = null ;
   if ((r_struserid.equals("steve")) && (r_strpassword.equals("15seconds")))
   {
    strroles = new string[1] ;
    strroles[0] = "administrator" ;
   }
   else if ((r_struserid.equals("mansoor")) && (r_strpassword.equals("mas")))
   {
    strroles = new string[1] ;
    strroles[0] = "user" ;
   }
   return strroles ;
  }
 }
}

  我們研究一下上面的代碼。

  我們是從init函數開始的。這個函數把處理程式的authenticaterequest事件插入application(應用程式)對象的事件處理程式列表中。這將導致引發authenticationrequest事件的時候application調用該方法。

  我們的http模塊初始化之後,我們就能夠調用他的authenticaterequest方法來鑑別客戶端請求。authenticaterequest方法是該安全/身份認證機制的核心。在這個函數中:

  1和2行提取httpapplication和httpcontext對象。3到7行檢測是否沒有給我們提供了用戶id或密碼。假如沒有提供,就顯示錯誤信息,請求處理過程終止。

  9到12行從httprequest對象中提取用戶id和密碼。

  14行調用一個叫做authenticateandgetroles的輔助(helper)函數。這個函數主要執行身份驗證並決定用戶角色。上面的代碼採用了硬編碼(hard-coded),只允許兩個用戶使用,但是我們能夠擴展這個方法,並添加代碼和用戶數據庫交互操作並檢索用戶的角色。

  16到19行檢測是否有角色和用戶關聯。假如沒有就意味着傳遞給我們的憑證沒有通過驗證;因此該憑證是無效的。因此,給客戶端發送一個錯誤信息,並且請求結束了。

  20和21行很重要,因爲這兩行實際上告訴asp.net http運行時已登錄用戶的身份。這兩行成功執行以後,我們的aspx頁面就能夠使用user對象訪問這些信息了。

  現在我們看一看這種身份驗證機制的運行情況。現在我們只允許下面兩個用戶登錄到系統:

  · user id = steve, password = 15seconds, role = administrator
  · user id = mansoor, password = mas, role = user

  注意用戶id和密碼是大小寫敏感的(區分大小寫)。

  首先試圖不提供憑證登錄系統,在ie中輸入http://localhost/webapp2/index.aspx將看到下面的消息:


  現在試圖使用用戶id“steve”和密碼“15seconds”登錄系統。輸入 http://localhost/webapp2/index.aspx?userid=steve&password=15seconds您將看到下面的歡迎消息:


  現在試圖使用用戶id“mansoor”和祕碼“mas”登錄系統。輸入aspx?userid=mansoor&password=mas">http://localhost/webapp2/index.aspx?userid=mansoor&password=mas您將看到下面的歡迎消息頁面:



  現在試圖使用錯誤的用戶id和密碼組合來登錄系統。輸入http://localhost/webapp2/index.aspx?userid=mansoor&password=xyz您將看到下面的錯誤消息:


  這表明我們的安全模塊在起作用了。您能夠通過在authenticateandgetroles方法中使用數據庫訪問代碼來擴展該安全模塊。

  要使任何的部分都起作用,我們必須對web.config文檔進行一些修改。首先,由於我們要使用自己的身份驗證,因此無需其他的身份驗證機制。爲了達到這個目的,改變webapp2的web.config文檔中的<authentication>節點,如下所示:

<authentication mode="none"/>

  類似地,不允許匿名用戶訪問我們的web站點。給web.config文檔添加下面的語句:

<authorization>
 <deny users="?"/>
</authorization>

  用於至少能夠匿名訪問用於提供憑證的文檔。在web.config文檔中使用下面的配置配置信息把index.aspx作爲唯一能夠匿名訪問的文檔:

<location path="index.aspx">
 <system.web>
  <authorization>
   <allow users="*"/>
  </authorization>
 </system.web>
</location>

  結論

  您可能已意識到有了http處理程式和模塊後,asp.net已給研發者提供了強大的能量。把您自己的組件插入asp.net請求處理管道,享受他的長處吧。

  作爲練習,您應該進一步改進程式,使示例身份驗證模塊更加靈活,並能根據用戶的需要進行調整
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章