【问题标题】:HttpModule EndRequest handler called twiceHttpModule EndRequest 处理程序调用了两次
【发布时间】:2012-09-17 15:00:27
【问题描述】:

我正在尝试为在 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 配置部分,则处理程序只被调用一次并且一切正常。

所以我有缓解措施,但我真的不喜欢我不理解的行为。为什么处理程序会被调用两次?

这是问题的最小重现:

Web.config:

  <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;
        }
    }
}

【问题讨论】:

  • 您在应用程序中使用 UrlRewrite 吗?看来,它会导致 EndRequest 触发两次。
  • 可以在后台开启吗?我的 web.config 中没有 UrlRewriteModule。
  • 我不这么认为。这真的很奇怪,因为我不能说我所有的问题都是由 UrlRewriteModule 引起的。但其中一些是。

标签: asp.net wcf azure


【解决方案1】:

当 ASP.net 应用程序启动时,为了最大限度地提高性能,ASP.NET Worker 进程将根据需要实例化尽可能多的 HttpApplication 对象。每个HttpApplication 对象,还将实例化每个注册的IHttpModule 的一个副本并调用Init 方法!这实际上是在 IIS(或 cassini 是 VS 内置的网络服务器)下运行的 ASP.NET 进程的内部设计。可能是因为您的 ASPX 页面包含指向浏览器将尝试下载的其他资源、外部资源和 iframe、css 文件或 ASP.NET 工作进程行为的链接。

幸运的是 Global.asax 并非如此:

Here's from MSDN:

Application_Start 和 Application_End 方法是特殊方法 不代表 HttpApplication 事件。 ASP.NET 调用它们一次 对于应用程序域的生命周期,而不是每个 HttpApplication 实例。

但是,在创建所有模块后,对于 HttpApplication 类的每个实例都会调用一次 HTTPModule's init 方法

第一次请求 ASP.NET 页面或进程时 应用程序,创建一个新的 HttpApplication 实例。然而,为了 最大化性能,HttpApplication 实例可能被重用于 多个请求。

并由下图说明:

如果您想要保证只运行一次的代码,您可以使用Application_StartGlobal.asax 或设置一个标志并将其锁定在底层模块中,这不认为是一个好习惯身份验证!

【讨论】:

  • 我只是设置runAllManagedModulesForAllRequests="true" 将导致为每个请求执行自定义模块,包括静态文件(JS/CSS/图像)。因此,对于我来说,单页加载两次似乎太少了。
  • 我不需要代码来运行一次;问题是我看到两个 EndRequests 用于对服务的单个请求。它不应该是 JS/CSS/Images,因为这是一个服务,而不是一个网页。我可以在 Fiddler 中看到只发出一个请求。问题是为什么我通过 Fiddler 看到一个请求,但在模块中看到两个请求,以及为什么如果我在 web.config 中复制配置,这个问题就会消失。
  • 通常你不需要复制配置,如果你使用 IIS7+ 配置必须在system.webServer 部分设置!模块的Init 方法是否调用一次,而EndRequest 是否为单个请求调用两次?因为EndRequest 每个请求不能被调用两次!但是我注意到您在 WCF 主机配置中启用了aspNetCompatibilityEnabled,您在测试什么网络服务器? IIS? IIS 快递? VS 网络服务器?如何在 IIS 中托管 WCF? WebServiceHost? .svc 文件?您可以在 EndRequest 中发布HttpContext.Current.Request.Path 吗?
  • 是的,每个 HttpApplication 都会调用一次 Init 模块。并且 EndRequest 被调用了两次。就我而言,UrlRewritingModule 负责。
  • HttpContext.Current.Request.Path 在这两种情况下都是“/”。我相信托管在 IIS Express 中。测试是在 VS 2010 和 2012 中,在 Azure Compute Emulator 中完成的,也只是直接调试 WCF 服务。我使用 Azure SDK 1.8 中的 Azure 模板创建了重现;我对项目所做的唯一更改是添加 AuthModule(如上)和匹配更改到 web.config 以注册 AuthModule。
【解决方案2】:

抱歉,不知道为什么它会被调用两次,但是 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           
            //.............         
        }
    }

【讨论】:

  • 是的,我不知道为什么我之前没有意识到我应该如何接近它(我想我一定认为我使用的样本做得“正确”),但我最终将逻辑移至 AuthenticateRequest、PostAuthenticateRequest 和 AuthorizeRequest 部分。不幸的是,这并没有回答为什么处理程序会以这种方式运行的问题。
猜你喜欢
  • 1970-01-01
  • 2017-06-27
  • 2018-10-23
  • 1970-01-01
  • 2011-08-29
  • 1970-01-01
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多