BeginRequest被调用了两次。

3

我创建了一个HttpModule

using System;
using System.Web;

public class TestModule : IHttpModule
{
    public TestModule() {  }

    public String ModuleName { get { return "TestModule"; } }

    public void Dispose() {  }

    public void Init(HttpApplication app)
    {
        app.BeginRequest += (new EventHandler(this.DoBeginRequest));
    }

    private void DoBeginRequest(Object source, EventArgs e)
    {
        HttpApplication application = (HttpApplication)source;
        HttpContext context = application.Context;
        context.Response.Write("<pre>Request URL: " + context.Request.FilePath + "</pre>");
        context.Response.Flush();            
        System.Diagnostics.Debug.WriteLine(context.Request.ToString());
    }
}

这样加载:

<system.webServer>
  <modules>
    <add name="TestModule" type="TestModule"/>
  </modules>
</system.webServer>

当我从 Web 浏览器或者使用 curl 命令调用这个函数时,输出面板中会打印两行日志信息,同时我也会看到下面的响应结果:

<pre>Request URL: /example</pre><pre>Request URL: /example</pre>

这表明每次都是相同的上下文。 为什么会发生这种情况? Request对象中有很多字段,但我还没有找到两个调用之间的任何区别。是否有某个属性应该被检查,以便给出一个“阶段”,我只回应其中一个?
我在这个站点上找到了几个相关问题,但大多数似乎倾向于是Web浏览器寻找另一个资源,例如favicon.ico,而这里并非如此。 MSDN对于BeginRequest的细节似乎不够详细,所以我到目前为止没有找到太多帮助。我可能漏掉了一些显而易见的东西,这是我第一次接触.NET/IIS等技术,通常我是一名Java开发人员。 更新: 我转储了Request的所有公共属性,这是两者之间的区别:
Headers == Accept=*%2f*&Host=localhost%3a2017&User-Agent=curl%2f7.35.0
Headers == Content-Length=0&Accept=*%2f*&Host=localhost%3a2017&User-Agent=curl%2f7.35.0

具体来说,在第二次调用时设置了 Content-Length 标头。 这在 curl 发出的请求中不存在。
> GET /example HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:2017
> Accept: */*

这是IIS试图提供帮助吗?在处理完整个请求后,它知道正文的长度(0),然后再次调用带有该设置的函数吗?

问题可能隐藏在很多地方... 我建议在 Response 值与字符串连接的地方设置一个断点,看看它何时会被触发第二次。 检查调用堆栈以查看是谁发起了第二次调用。 - Nikita
有没有办法将IISExpress置于某种调试模式中?在调用堆栈中,我只看到DoBeginRequest之前是[External code] - Stik
非常有趣的IIS行为...你可能会发现这篇文章很有用,但其中的解决方案在我们的情况下不起作用:http://erraticdev.blogspot.com/2011/01/how-to-correctly-use-ihttpmodule-to.html - Nikita
3个回答

5

好的,在经历了很多沮丧后,我发现特定的URL并不会遇到这种行为。带有“扩展名”的URL不会出现这种情况:

curl http://localhost:2017/test
 > BeginRequest
 > BeginRequest 

curl http://localhost:2017/test.abc
 > BeginRequest

curl http://localhost:2017/.a
 > BeginRequest

通过在线搜索,我找到了一个名为ExtensionlessUrl-Integrated-4.0的处理程序,它由主配置文件applicationhost.config加载。我不太清楚这是在做什么,也不知道为什么会导致请求重复,但在我的web.config中明确删除它已解决了这个问题:

   <system.webServer>
      <handlers>
        <remove name="ExtensionlessUrl-Integrated-4.0" />
      </handlers>
      <modules>
        <add name="TestModule" type="TestModule"/>
      </modules>
    </system.webServer>

我必须承认,我有点担心以后可能会遇到其他类似的陷阱 - applicationhost.config 加载了很多其他处理程序和模块,其中任何一个都可能在我的模块能够访问它之前搞乱这样的东西。但这是另一天的问题...


非常棒的观察和不错的解决方案。我在SO上读了将近15个问题,其中没有一个提供正确的解释或解决方案。我会在每个问题下发表这个观察,以获得认可。 - AaA
然而,我不得不删除其中的3个(Windows 10/Server 2019),即ExtensionlessUrlHandler-ISAPI-4.0_32bitExtensionlessUrlHandler-ISAPI-4.0_64bitExtensionlessUrlHandler-Integrated-4.0。同时,仍然需要将文件夹重定向到文件夹的默认文档。 - AaA

1
(注:此解决方案可能不适用于每个应用程序)
只需在事件处理程序的末尾(无论是BeginRequest、PostAuthorizeRequest还是其他什么)调用CompleteRequest()即可。这个调用完成请求处理并防止其他处理程序工作,但如果您已经处理了它,很可能您不希望进行后处理,所以没问题。

0

使用LogRequest事件监听器而不是BeginRequest应该适合你的目的:

public void Init(HttpApplication app)
{
    app.LogRequest += DoBeginRequest; // TODO: Rename your function
}

是的,我在非常干净的ASP.NET Web应用程序中看到了你的行为。 这与 favicon.ico 的额外请求无关。 它可能涉及到IIS在应用程序池中创建的多个应用程序实例。以及多个实例的模块对同一事件的多个订阅。这里有些问题...但目前我不能在我的代码中证明这些理论中的任何一个。


使用LogRequest似乎表现正常,但用它来执行实际工作感觉不太对(在这种情况下,我将使用它来通过某种IPC机制有效地代理到另一个进程)。我也不确定是否足够理解LogRequest以便能够依赖它的工作! - Stik
经过进一步调查,LogRequest 被调用了两次,但只有一个 Response 流会返回给客户端。IIS 总是这么难吗?我觉得这个应该是一个非常简单的 "Hello World" 应用程序,但仍然有很大问题,我肯定是漏掉了什么明显的东西。 - Stik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接