ASP.NET中的HTTP模組和處理常式
最後更新:2017-02-28
來源:互聯網
上載者:User
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請求處理管道,享受它的優點吧。
作為練習,你應該進一步改進程式,使樣本身分識別驗證模組更加靈活,並能根據使用者的需要進行調整。