【问题标题】:Apache/Tomcat error - wrong pages being deliveredApache/Tomcat 错误 - 传递错误的页面
【发布时间】:2010-09-19 18:42:42
【问题描述】:

这个错误让我发疯了。我们有一台运行 Apache 和 Tomcat 的服务器,为多个不同的站点提供服务。通常服务器运行良好,但有时会在为人们提供错误页面时发生错误 - 其他人请求的页面!

线索:

  • 正在传送的页面是其他用户最近请求的页面,否则会正确传送。众所周知,两个同时请求被交换。据我所知,错误传递的页面都不会超过几分钟。
  • 它只影响 Tomcat 服务的文件。图片等静态文件不受影响。
  • 它不会一直发生。当它发生时,它会发生在每个人身上。
  • 这似乎发生在需求高峰期。但是,需求还不是很高——它肯定在 Apache 可以应付的范围内。
  • 重新启动 Tomcat 修复了它,但只维持了几分钟。重新启动 Apache 修复了它,但只有几分钟。
  • 服务器正在运行 Apache 2 和 Tomcat 6,在 Gentoo 上使用 Java 6 VM。与 AJP13 的连接,<VirtualHost> 块中的 JkMount 指令是正确的。
  • 任何日志文件都没有任何用处。

更多信息:

Apache 没有开启任何形式的缓存。 httpd.conf 和相关导入中所有与缓存相关的条目都说,例如:

<IfDefine CACHE>
  LoadModule cache_module modules/mod_cache.so
</IfDefine>

虽然 Apache 的选项不包括该标志:

APACHE2_OPTS="-D DEFAULT_VHOST -D INFO -D LANGUAGE -D SSL -D SSL_DEFAULT_VHOST -D PHP5 -D JK"

Tomcat 也没有打开缓存选项,我可以找到。

toolkit's suggestion 很好,但在这种情况下不合适。是什么让我相信错误不可能出现在我自己的代码中,因为它不仅仅是正在传输的几个值 - 它是整个请求,包括 URL、参数、会话 cookie 等等。人们正在返回显示“您以 John 身份登录”的页面,而他们显然不是。


更新:

根据几个人的建议,我将在 Tomcat 服务的页面中添加以下 HTTP 标头以禁用所有形式的缓存:

Cache-Control: no-store
Vary: *

希望这些标头不仅会受到 Apache 的尊重,而且还会受到任何其他可能阻碍的缓存或代理的尊重。不幸的是我没有办法故意重现这个错误,所以我只能等着看它是否会再次出现。

我注意到包含以下标题 - 它们可以以任何方式相关吗?

Connection: Keep-Alive
Keep-Alive: timeout=5, max=66

更新:

显然,这在我睡着的时候又发生了,但现在我醒着看到它已经停止发生了。同样,我可以看到日志中没有任何有用的信息,因此我不知道实际发生了什么或如何防止它。

我可以在 Apache 或 Tomcat 的日志中添加任何额外的信息,以便更容易诊断吗?


更新:

由于这种情况再次发生了几次,我们已经更改了 Apache 连接到 Tomcat 的方式,以查看它是否会影响事情。我们使用mod_jk 和这样的指令:

JkMount /portal ajp13

我们现在改用mod_proxy_ajp,如下所示:

ProxyPass /portal ajp://localhost:8009/portal

我们会看看它是否有任何不同。这个错误总是令人讨厌地无法预测,所以我们永远无法确定它是否有效。


更新:

我们刚刚在使用mod_jk 留下的站点上短暂收到错误,而同一服务器上使用mod_proxy_ajp 的姊妹站点没有显示错误。这并不能证明什么,但它确实提供了证据表明切换到 mod_proxy_ajp 可能有所帮助。


更新:

昨晚我们刚刚在使用mod_proxy_ajp 的网站上再次遇到错误,很明显这并没有解决它 - mod_jk 不是问题的根源。我将尝试关闭持久连接的匿名建议:

KeepAlive Off

如果这也失败了,我会绝望地开始调查 GlassFish。


更新:

该死!问题刚刚回来。我有一段时间没看到它了,所以我开始认为我们终于把它整理好了。我讨厌海森虫。

【问题讨论】:

  • 为了完全消除缓存的可能性,您可以在所有请求前插入一个servlet过滤器,根据mnot.net/cache_docs设置适当的响应头
  • 这不是一个坏主意。我还可以向 Apache 添加显式指令,以防止缓存任何相关地址。它可能无法解决任何问题,但它可以做的最糟糕的事情是帮助消除选项。
  • 我会倾向于你的应用正在做的事情。日志应该向您显示交换页面的时间。我认为最多的线索将来自于追求交换页面的场景。
  • 这当然是我的第一个想法,但我已经多次检查我的代码,但没有找到任何东西。就像我说的,被交换的不仅仅是几个对象,而是整个请求上下文、cookies 等等。
  • 马库斯你找到什么了吗?我也面临同样的问题,但只有一次客户用打印屏幕报告过。我正在使用 JDK6、Tomcat 6、Struts 1.0 和磁贴。我无法复制或看到代码中的任何问题。我跟踪了我们的代码完成的日志跟踪,看起来tomcat确实混淆了会话并同时显示了其他用户请求的页面。两者之间不涉及 Apache Web 服务器。

标签: apache tomcat ajp


【解决方案1】:

这可能是您的 servlet 的线程安全性吗?

您的 servlet 是否在实例成员中存储任何信息。

例如,像以下这样简单的事情可能会导致与线程相关的问题:

public class MyServlet ... {
    private String action;

    public void doGet(...) {
         action = request.getParameter("action");
         processAction(response);
    }

    public void processAction(...) {
         if (action.equals("foo")) {
             // send foo page
         } else if (action.equals("bar")) {
             // send bar page
         }
     }
}

因为 serlvet 是多线程访问的,所以不能保证 action 实例成员不会被别人的请求破坏,最终返回错误的页面。

解决这个问题的简单方法是在实例成员中使用局部变量:

public class MyServlet ... {
    public void doGet(...) {
         String action = request.getParameter("action");
         processAction(action, response);
    }

    public void processAction(...) {
         if (action.equals("foo")) {
             // send foo page
         } else if (action.equals("bar")) {
             // send bar page
         }
     }
}

注意:这也延伸到 JavaServer Pages,如果您为视图分派给它们?

【讨论】:

  • 好主意,但不,我不相信。我不保留任何共享状态(除了极少数缓存的数据库对象,我小心翼翼地确保线程安全)
  • 我支持你,因为虽然它没有解决我的问题,但对于登陆这里的其他人来说,这不是一个糟糕的起点。
【解决方案2】:

你确定这是别人请求的页面还是没有参数的页面?, 如果您在 apache 后面的 tomcat 服务器上的 server.xml 上的 connectionTimeout 太短,您可能会遇到奇怪的错误,请将其增加到更大的数字:

默认配置:

  <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

改变了:

  <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="2000000"
               redirectPort="8443" />

【讨论】:

  • 它肯定是在返回其他人的数据,基于诸如“您以 John 身份登录”之类的行。但是自从这个问题相关以来已经过去了两年。
【解决方案3】:

虽然您确实提到在您的设置中未启用 mod_cache,但对于其他可能遇到启用 mod_cache 的相同问题的其他人(即使在静态内容上),解决方案是确保在 Set-Cookie 上启用以下指令HTTP 标头:

CacheIgnoreHeaders Set-Cookie

mod_cache 的原因将缓存可能提供给其他用户的 Set-Cookie 标头。然后,这会将会话 ID 从上次填充缓存的用户泄漏给另一个用户。

【讨论】:

    【解决方案4】:

    这可能根本不是缓存问题。尝试增加 apache2.conf 中的 MaxClients 参数。如果它太低(默认为 150?),Apache 开始对请求进行排队。当它决定通过 mod_proxy 为排队的请求提供服务时,它会拉出一个错误的页面(或者可能只是在进行所有排队时压力很大)。

    【讨论】:

    • 我同意它可能不是缓存,但有证据表明使用的代理方法会有所不同。而且只有几个用户可以看到问题,所以我看不出他们如何收集 150 个并发请求。不过,这通常是个好建议。
    【解决方案5】:

    我们将 Apache 从使用 AJP 代理切换到使用 HTTP 代理。到目前为止,它似乎已经解决了这个问题,或者至少大大减少了它 - 几个月来没有报告过这个问题,并且从那时起该应用的使用量有所增加。

    更改在 Apache 的 httpd.conf 中。从mod_jk开始:

    JkMount /portal ajp13
    

    我们切换到mod_proxy_ajp

    ProxyPass /portal ajp://localhost:8009/portal
    

    然后终于直奔mod_proxy

    ProxyPass /portal http://localhost:8080/portal
    

    您需要确保将 Tomcat 设置为在端口 8080 上服务 HTTP。请记住,如果您服务于 /,则需要在代理的两侧都包含 /,否则它会开始哭泣:

    ProxyPass / http://localhost:8080/
    

    【讨论】:

      【解决方案6】:

      看看这个网站,它描述了 mod_jk 的一个问题。在查看一个非常相似的问题时,我偶然发现了您的帖子。基本上解决方法是升级到较新版本的 mod_jk。我还没有机会在我们的服务器中实施更改,但是我明天会尝试一下,看看它是否有帮助。

      http://securitytracker.com/alerts/2009/Apr/1022001.html

      【讨论】:

      • 谢谢!但是为什么我们会遇到与 mod_proxy_ajp 而不是 mod_jk 相同的问题?
      • 不确定,但我们也遇到了这个问题。我们尝试了那篇文章中的建议并升级到最新版本的 isapi 驱动程序,但我们仍然遇到问题。您找到了解决方案还是改用了 Glassfish?
      • 我们已改用直接代理而不是 AJP,如下所述:stackoverflow.com/questions/956361/…
      • 到目前为止,问题还没有再次出现,但这并不能证明问题已经解决。而且使用直接代理确实会产生轻微的性能成本。
      【解决方案7】:

      8 次更新问题稍后再使用一个问题来测试/重现,尽管对于公共网站来说可能很困难(或昂贵)。

      您可以在网站上启用 https。这至少会清除沿途的任何其他代理缓存。如果路上有一些被遗忘的负载均衡器或公司缓存会干扰您的流量,那就太糟糕了。

      对于公共网站,这意味着密钥上有受信任的证书,因此会涉及一些资金。对于测试自签名密钥可能就足够了。此外,请检查是否不涉及解密和重新加密流量的透明代理。 (它们很容易被检测到,因为它们不能使用与原始服务器相同的证书/密钥)

      【讨论】:

      • 有趣。我可能至少可以在网站的登录部分启用 HTTPS,如果/当问题再次发生时,我将测试 HTTPS 交付的部分是否受到影响。这可能会有所帮助,谢谢。
      【解决方案8】:

      试试这个:

      response.setHeader("Cache-Control", "no-cache"); //HTTP 1.1
      response.setHeader("Pragma", "no-cache"); //HTTP 1.0
      response.setDateHeader("Expires", 0); //prevents caching at the proxy server
      

      【讨论】:

        【解决方案9】:

        我遇到了这个问题,它真的让我发疯了。我不知道为什么,但我解决了它关闭 http.conf 上的 Keep Alive

        来自

        保持活跃

        保持活动关闭

        我的应用程序没有使用 keepalive 功能,所以对我来说效果很好。

        【讨论】:

        • 尽管结果看似积极,但我现在已经确定 KeepAlive 不是问题所在。 :(
        【解决方案10】:

        检查您的标头是否允许在没有正确 Vary HTTP 标头的情况下进行缓存(例如,如果您使用会话 cookie 并允许缓存,则您需要在 Vary HTTP 标头中为 cookie 标头或缓存添加一个条目/proxy 可能会为一个用户提供一个页面的缓存版本给另一个用户)。

        问题可能不在于您的 Web 服务器上的缓存,而在于另一层缓存(在您的 Web 服务器前面的反向代理上,或在用户附近的代理上)。如果客户端是 NAT,它们也可能位于透明代理之后(并且,为了使调试更加困难,透明代理可能被配置为在标头中不可见)。

        【讨论】:

        • 有什么方法可以找出我的页面在哪里以及如何被缓存的?
        • 除非缓存添加了额外的 HTTP 标头(主要是 Via 和 X-Forwarded-For),或者您控制客户端并可以尝试 tcptraceroute 或 HTTP TRACE,否则您不能。跨度>
        • 自从我遵循了这个建议,问题显然已经解决了。我仍然不知道原因,但无论如何我都会将此标记为答案。
        • 很抱歉不接受您的回答,但问题又回来了。
        【解决方案11】:

        我不是专家,但这会是一些奇怪的Network Address Translation 问题吗?

        【讨论】:

        • 有趣的想法。服务器不在 NAT 之后(我们所有的服务器都有全局 IP),但客户端当然是。我想知道 Apache 有没有什么特别的东西来适应它?
        猜你喜欢
        • 1970-01-01
        • 2014-10-19
        • 2011-06-07
        • 1970-01-01
        • 1970-01-01
        • 2013-06-20
        • 1970-01-01
        • 2020-11-06
        • 1970-01-01
        相关资源
        最近更新 更多