Asp.net

HttpModule EndRequest 處理程序呼叫了兩次

  • November 7, 2012

我正在嘗試為在 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" />
   &lt;!--<httpModules>
     &lt;add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/&gt;
   &lt;/httpModules&gt;--&gt;
 &lt;/system.web&gt;
 &lt;system.serviceModel&gt;
   &lt;behaviors&gt;
     &lt;endpointBehaviors&gt;
       &lt;behavior name="WebBehavior"&gt;
         &lt;webHttp/&gt;
       &lt;/behavior&gt;
     &lt;/endpointBehaviors&gt;
     &lt;serviceBehaviors&gt;
       &lt;behavior&gt;
         &lt;!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --&gt;
         &lt;serviceMetadata httpGetEnabled="true" /&gt;
         &lt;!-- 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 --&gt;
         &lt;serviceDebug includeExceptionDetailInFaults="true"/&gt;
       &lt;/behavior&gt;
     &lt;/serviceBehaviors&gt;
   &lt;/behaviors&gt;
   &lt;serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" /&gt;
   &lt;services&gt;
     &lt;service name="TestWCFRole.Service1"&gt;
       &lt;endpoint binding="webHttpBinding" name="RestEndpoint" contract="TestWCFRole.IService1" bindingConfiguration="HttpSecurityBinding" behaviorConfiguration="WebBehavior"/&gt;
       &lt;host&gt;
         &lt;baseAddresses&gt;
           &lt;add baseAddress="http://localhost/" /&gt;
         &lt;/baseAddresses&gt;
       &lt;/host&gt;
     &lt;/service&gt;
   &lt;/services&gt;
   &lt;standardEndpoints&gt;
     &lt;webHttpEndpoint&gt;
       &lt;standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/&gt;
     &lt;/webHttpEndpoint&gt;
   &lt;/standardEndpoints&gt;
   &lt;bindings&gt;
     &lt;webHttpBinding&gt;
       &lt;binding name="HttpSecurityBinding" &gt;
         &lt;security mode="None" /&gt;
       &lt;/binding&gt;
     &lt;/webHttpBinding&gt;
   &lt;/bindings&gt;
 &lt;/system.serviceModel&gt;
 &lt;system.webServer&gt;
   &lt;modules runAllManagedModulesForAllRequests="true"&gt;
     &lt;add name="AuthModule" type="TestWCFRole.AuthModule, TestWCFRole"/&gt;
   &lt;/modules&gt;
   &lt;directoryBrowse enabled="true"/&gt;
 &lt;/system.webServer&gt;

Http模組:

using System;
using System.Web;

namespace TestWCFRole
{
   public class AuthModule : IHttpModule
   {
       /// &lt;summary&gt;
       /// 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
       /// &lt;/summary&gt;
       #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 並非如此:

這裡來自 MSDN

Application_Start 和 Application_End 方法是不代表 HttpApplication 事件的特殊方法。ASP.NET 在應用程序域的生命週期內呼叫它們一次,而不是為每個 HttpApplication 實例呼叫一次。

但是HTTPModule's init,在創建所有模組後,為 HttpApplication 類的每個實例呼叫一次方法

第一次在應用程序中請求 ASP.NET 頁面或程序時,會創建一個新的 HttpApplication 實例。但是,為了最大限度地提高性能,HttpApplication 實例可能會被重複用於多個請求。

並由下圖說明: 在此處輸入圖像描述

如果您想要保證只執行一次的程式碼,您可以使用Application_StartGlobal.asax設置一個標誌並將其鎖定在底層模組中,這對於身份驗證來說不是一個好習慣!

引用自:https://stackoverflow.com/questions/12594043