介紹
在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”登錄系統。輸入
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請求處理管道,享受它的優點吧。
作為練習,你應該進一步改進程序,使示例身份驗證模塊更加靈活,並能根據用戶的需要進行調整。