HttpModule EndRequest 處理程序呼叫了兩次
我正在嘗試為在 WCF 中實現並託管在 Azure 上的 REST 服務實施身份驗證。我正在使用 HttpModule 來處理 AuthenticationRequest、PostAuthenticationRequest 和 EndRequest 事件。如果缺少 Authorization 標頭或其中包含的令牌無效,則在 EndRequest 期間,我將 Response 上的 StatusCode 設置為 401。但是,我確定 EndRequest 被呼叫了兩次,並且在第二次呼叫時,響應已經具有標頭設置,導致設置 StatusCode 的程式碼拋出異常。
我在 Init() 中添加了鎖,以確保處理程序沒有被註冊兩次;還是跑了兩次。Init() 也執行了兩次,表明正在創建 HttpModule 的兩個實例。但是,在 VS 調試器中使用 Set Object ID 似乎表明請求實際上是不同的請求。我已經在 Fiddler 中驗證了瀏覽器只向我的服務發出了一個請求。
如果我切換到使用 global.asax 路由而不是依賴於 WCF 服務主機配置,則處理程序只呼叫一次並且一切正常。
如果我將配置添加到 system.web 配置部分以及 Web.config 中的 system.webServer 配置部分,則處理程序只呼叫一次,一切正常。
所以我有緩解措施,但我真的不喜歡我不理解的行為。為什麼處理程序被呼叫兩次?
這是該問題的最小重現:
網路配置:
<system.web> <compilation debug="true" targetFramework="4.0" /> <!--<httpModules> <add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/> </httpModules>--> </system.web> <system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="WebBehavior"> <webHttp/> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior> <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> <serviceMetadata httpGetEnabled="true" /> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" /> <services> <service name="TestWCFRole.Service1"> <endpoint binding="webHttpBinding" name="RestEndpoint" contract="TestWCFRole.IService1" bindingConfiguration="HttpSecurityBinding" behaviorConfiguration="WebBehavior"/> <host> <baseAddresses> <add baseAddress="http://localhost/" /> </baseAddresses> </host> </service> </services> <standardEndpoints> <webHttpEndpoint> <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/> </webHttpEndpoint> </standardEndpoints> <bindings> <webHttpBinding> <binding name="HttpSecurityBinding" > <security mode="None" /> </binding> </webHttpBinding> </bindings> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/> </modules> <directoryBrowse enabled="true"/> </system.webServer>Http模組:
using System; using System.Web; namespace TestWCFRole { public class AuthModule : IHttpModule { /// <summary> /// You will need to configure this module in the web.config file of your /// web and register it with IIS before being able to use it. For more information /// see the following link: http://go.microsoft.com/?linkid=8101007 /// </summary> #region IHttpModule Members public void Dispose() { //clean-up code here. } public void Init(HttpApplication context) { // Below is an example of how you can handle LogRequest event and provide // custom logging implementation for it context.EndRequest += new EventHandler(OnEndRequest); } #endregion public void OnEndRequest(Object source, EventArgs e) { HttpContext.Current.Response.StatusCode = 401; } } }
抱歉,不知道為什麼它會被呼叫兩次,但是 EndRequest 最終可能會因為多種原因被呼叫。請求完成,請求被中止,發生了一些錯誤。所以我不會相信如果你到達那裡,你實際上有一個 401,這可能是出於其他原因。
我只是將我的邏輯保留在 AuthenticateRequest 管道中:
public class AuthenticationModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication context) { context.AuthenticateRequest += Authenticate; } public static void Authenticate(object sender, EventArgs e) { // authentication logic here //............. if (authenticated) { HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(myUser, myRoles); } // failure logic here //............. } }
當 ASP.net 應用程序啟動時,為了最大限度地提高性能,ASP.NET Worker 程序將
HttpApplication根據需要實例化盡可能多的對象。每個HttpApplication對象,還將實例化每個IHttpModule已註冊的副本並呼叫 Init 方法!這實際上是在 IIS(或 cassini 是 VS 內置的網路伺服器)下執行的 ASP.NET 程序的內部設計。可能是因為您的 ASPX 頁麵包含指向瀏覽器將嘗試下載的其他資源、外部資源和 iframe、css 文件或 ASP.NET 工作程序行為的連結。幸運的是,Global.asax 並非如此:
Application_Start 和 Application_End 方法是不代表 HttpApplication 事件的特殊方法。ASP.NET 在應用程序域的生命週期內呼叫它們一次,而不是為每個 HttpApplication 實例呼叫一次。
但是
HTTPModule's init,在創建所有模組後,為 HttpApplication 類的每個實例呼叫一次方法第一次在應用程序中請求 ASP.NET 頁面或程序時,會創建一個新的 HttpApplication 實例。但是,為了最大限度地提高性能,HttpApplication 實例可能會被重複用於多個請求。
並由下圖說明:
如果您想要保證只執行一次的程式碼,您可以使用
Application_Start或Global.asax設置一個標誌並將其鎖定在底層模組中,這對於身份驗證來說不是一個好習慣!
