【问题标题】:HttpModule Init method is called several times - why?多次调用 HttpModule Init 方法 - 为什么?
【发布时间】:2010-11-11 13:44:34
【问题描述】:

我正在创建一个 http 模块,在调试时我注意到一开始(至少)看起来很奇怪的行为。

当我在 httpmodule 的 init 方法中设置断点时,我可以看到 http module init 方法 被多次调用,即使我只是启动网站进行调试并做了一个单个请求(有时只被命中 1 次,有时多达 10 次)。

我知道我应该期望 HttpApplication 的多个实例正在运行,并且将为每个实例创建 http 模块,但是当我请求单个页面时,它应该由单个 http 应用程序处理对象,因此只触发一次关联的事件,但它仍然为每个请求触发事件多次,这是没有意义的 - 除了它必须在该 httpApplication 中添加多次 - 这意味着它是每次都调用相同的 httpmodule init 方法,而不是每次遇到断点时都创建一个新的 http 应用程序(参见底部的代码示例等)。

这里可能出了什么问题?是不是因为我在调试,在http模块中设置了断点?

它已经注意到,如果我启动网站进行调试并快速跨过 httpmodule 中的断点,它似乎只会命中一次 init 方法, 也是如此事件处理程序。如果我让它在断点处挂起几秒钟,则 init 方法 将被调用多次(似乎这取决于我在越过断点之前等待的时间)。也许这可能是一些内置功能,以确保 httpmodule 已初始化并且 http 应用程序可以服务请求,但它似乎也可能产生灾难性后果。

这似乎是合乎逻辑的,因为它可能正在尝试完成请求,并且由于我设置了断点,它认为出现问题并再次尝试调用 init 方法?那么它可以处理请求吗?

但这是正在发生的事情吗?一切都很好(我只是在猜测),还是一个真正的问题?

我特别担心的是,如果某些东西使它在“生产/实时”服务器上挂起几秒钟,则会通过 init 添加大量事件处理程序,因此每个请求都会页面突然触发 eventhandler 几次。

这种行为可能会迅速导致任何网站宕机。

我查看了用于 formsauthentication 和 rolemanagermodule 等的 httpmodules 的“原始”.net 代码……但我的代码并没有什么不同这些模块使用。

我的代码如下所示。

public void Init(HttpApplication app)
{
    if (CommunityAuthenticationIntegration.IsEnabled)
    {
        FormsAuthenticationModule formsAuthModule = (FormsAuthenticationModule) app.Modules["FormsAuthentication"];         

        formsAuthModule.Authenticate += new FormsAuthenticationEventHandler(this.OnAuthenticate);
    }
}

这是一个示例,它是如何在 .NET 框架的 RoleManagerModule 中完成的:

public void Init(HttpApplication app)
{
    if (Roles.Enabled)
    {
        app.PostAuthenticateRequest += new EventHandler(this.OnEnter);
        app.EndRequest += new EventHandler(this.OnLeave);
    }
}

有人知道怎么回事吗?

(我只是希望有人能告诉我为什么会这样,并向我保证一切都很好):)


更新:

我试图缩小问题的范围,到目前为止我发现被调用的 init 方法总是在我的 http 模块的一个新对象上(与我之前的想法相反)。

我似乎对于第一个请求(启动站点时),所有正在创建的 HttpApplication 对象及其模块都试图为第一个请求提供服务,因此都命中了 正在添加的事件处理程序。 我真的不明白为什么会这样。

如果我请求另一个页面,所有 HttpApplication 创建的(及其模块)将再次尝试服务请求,导致它多次点击 eventhandler。

但似乎如果我随后跳回第一页(或另一页),只有一个 HttpApplication 将开始处理请求,并且一切都按预期进行 - 只要我不要让它在断点处挂起。

如果我让它挂在断点上,它会开始创建新的 HttpApplication 对象并开始添加 HttpApplications(超过 1 个)来服务/处理请求( HttpApplication 正在为它提供服务,该 HttpApplication 当前在断点处停止)。

我猜想或希望这可能是一种帮助分配和处理负载和/或错误的智能“幕后”方式。但我不知道。 我希望那里的一些人可以向我保证它非常好,它应该是怎样的?

【问题讨论】:

  • 我在我们的 HttpModule 中也看到了这种行为
  • 约翰:我忘了关这个。它发生(至少在我的情况下)是因为模块被浏览器对任何资源(图像、javascript、样式表)发出的每个请求所击中。它被击中的原因是 etc MVC 中使用的路由模块。所有请求都需要由路由模块处理,因此不幸的是所有请求都命中了该模块。我已经问过这个问题,但没有人回答。

标签: c# .net httpmodule init


【解决方案1】:

Init() 方法被多次调用是正常的。当应用程序启动时,ASP.NET Worker 进程将实例化它认为需要的任意数量的 HttpApplication 对象,然后将它们池化(例如,将它们重用于新请求,类似于数据库连接池)。

现在对于每个HttpApplication 对象,它还将实例化每个已注册的IHttpModule 的副本并多次调用Init 方法。因此,如果创建了 5 个 HttpApplication 对象,则会创建 5 个 IHttpModule 副本,并且您的 Init 方法将被调用 5 次。有意义吗?

现在为什么要实例化 5 个HttpApplication 对象?好吧,也许您的 ASPX 页面包含指向您的浏览器将尝试下载的其他资源的链接,例如 css、javascript、WebResource.aspx,也可能是某处的 iframe。或者,也许 ASP.NET 工作进程“有心情”启动超过 1 个HttpApplication 对象,这实际上是在 IIS(或 VS 内置网络服务器)下运行的 ASP.NET 进程的内部细节/优化。

如果您想要保证只运行一次的代码(并且不想在 Global.asax 中使用 Application_StartUp 事件),您可以在 IHttpModule 中尝试以下操作:

private static bool HasAppStarted = false;
private readonly static object _syncObject = new object();

public void Init(HttpApplication context)
{
    if (!HasAppStarted)
    {
        lock (_syncObject)
        {
            if (!HasAppStarted)
            {
                // Run application StartUp code here

                HasAppStarted = true;
            }
        }
    }
}

我做过类似的事情,而且似乎很有效,但我欢迎对我的工作提出批评,以防我遗漏了什么。

【讨论】:

  • 这里是用Interlocked.CompareExchange代替锁的地方:)
  • 是否甚至请求资源文件会导致创建应用程序类及其自己的模块,从而触发此事件?
【解决方案2】:
  1. 检查 HttpContext.Current.Request 以查看触发模块的 init 的请求。可能是浏览器发送多个请求。

  2. 如果您连接到 IIS,请检查 IIS 日志以了解在您停留在断点时是否收到任何请求。

【讨论】:

  • 正确。发生这种情况是因为所有请求(对于任何资源)都命中了模块。就我而言,因为它似乎是路由模块。
【解决方案3】:

这里有一些关于您应该使用什么、何时以及如何工作的解释。 When to use Application_Start vs Init in Global.asax?

编辑:更多阅读

The ASP Column: HTTP Modules

INFO: Application Instances, Application Events, and Application State in ASP.NET

【讨论】:

  • 感谢您的回复。我不使用它们中的任何一个。全局 asax 中的 init 覆盖 HttpApplication 中的 init。我的问题是我的 httpmodule 中的 Init 方法被多次调用。据我了解,它应该为每个 http 应用程序创建 http 模块。在每个请求中,实例化的 HttpApplication 之一将处理请求,并触发添加的事件,但由于某种原因,事件处理程序 sometimes 被多次添加为同一 http 模块上的 init 方法(不是一个为其他 httpapplication 新创建的)称为几次。
【解决方案4】:

上面的示例为所有请求锁定 IHttpModule,然后冻结整个应用程序。 如果您的 IHttpModule 多次调用请求,则需要调用 HttpApplication 方法 CompleteRequest 并在 EndRequest 事件中处理 IHttpModule 的 HttpApplication 实例,以便像这样删除 HttpApplication 的实例:

public class TestModule :IHttpModule
    {
        #region IHttpModule Members

        public void Dispose()
        {

        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(context_BeginRequest);
            context.EndRequest += new EventHandler(context_EndRequest);
        }

        void context_EndRequest(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            app.CompleteRequest();
            app.Dispose();
        }

        void context_BeginRequest(object sender, EventArgs e)
        {
            //your code here
        }

        #endregion
    }

如果您每次都需要 IHttpModule 请求而不在回发时重新请求,请使用上面的代码。

【讨论】:

  • 这点很好,但是对于某些应用程序(比如我的)来说,要执行的代码最多只有几微秒,所以锁绝对是划算的。但是感谢您的想法,我认为知道这将改善我的编码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-02
  • 2020-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多