禁用Tomcat中所有默认的HTTP错误响应内容

73

默认情况下,当Tomcat遇到类似HTTP 404的情况时,会向客户端发送一些HTML内容。我知道可以通过web.xml中的<error-page>配置来自定义此内容。

但是,我希望Tomcat在响应内容方面什么也不发送(当然我仍需要状态代码)。有没有简单的方法来配置这个呢?

我想避免A)从我的Servlet显式地向响应流发送空内容;B)为我的web.xml中的一堆HTTP错误状态配置自定义错误页面。

背景:我正在开发一个HTTP API,并控制自己的响应内容。例如,在HTTP 500的情况下,我会在响应中填入包含错误信息的XML内容。对于HTTP 404之类的情况,HTTP响应状态对于客户端足够,Tomcat发送的内容是不必要的。如果有其他方法,我愿意听取意见。

编辑: 经过持续调查,我仍然找不到很好的解决方案。如果有人确切地说这是不可能的,或者提供一个资源并证明它行不通,那么我将接受它作为答案并尝试绕过它。


7
我没有过度解析代码的含义,我是按照它们预期的使用。这是为了一个REST API - 例如,如果有人在我的API中对某个资源执行GET操作,但我找不到该资源,我会将响应状态设置为404。如果发生某种奇怪的错误,我会设置500状态并在响应中提供一些错误内容。但我想要对此内容进行独立控制 - 我不希望Tomcat返回HTML或其他任何内容。如果需要返回内容,我希望我的Servlet负责返回。 - Rob Hruska
4
我发现 Servlet 3 可以使用一个通用的 <error-page> 来捕捉所有错误:http://static.springsource.org/spring/docs/3.2.0.BUILD-SNAPSHOT/reference/html/mvc.html#mvc-ann-customer-servlet-container-error-page - Erich Eichinger
@ErichEichinger - 非常有用的信息,感谢您的分享。 - Rob Hruska
8个回答

48
如果您不希望Tomcat显示错误页面,则不要使用sendError(...),而应该使用setStatus(...)。
例如,如果您想提供405响应,则可以这样做。
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);      
response.getWriter().println("The method " + request.getMethod() + 
   " is not supported by this service.");

同时请记住不要从您的servlet中抛出任何异常。相反,捕获异常并再次自行设置statusCode。

例如:

protected void service(HttpServletRequest request,
      HttpServletResponse response) throws IOException {
  try {

    // servlet code here, e.g. super.service(request, response);

  } catch (Exception e) {
    // log the error with a timestamp, show the timestamp to the user
    long now = System.currentTimeMillis();
    log("Exception " + now, e);
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    response.getWriter().println("Guru meditation: " + now);
  }
}

当然,如果你不想要任何内容,那么就不写任何东西给写入器,只需设置状态即可。


3
我认为这个方法起作用的真正原因是你已经将内容写入了输出流中。我相信,如果你只是设置状态而不向响应中写入任何内容,Tomcat会发送405默认的错误响应。 - matt b
1
仅仅设置状态码似乎不足以抑制标准错误页面的显示(至少根据我的经验是这样的)。 - Greg Brown
1
我必须在写入器上调用 flush() 方法,以确保我的内容不会被 Tomcat 覆盖。 - Michele Palmia
此外,这仅适用于空响应,至少具有500响应代码。 - mjs
1
捕获异常似乎在抛出ClassNotFoundException时无效。错误仍然显示。 - user2914191
显示剩余2条评论

46

虽然这并不完全回应问题中的“不发送任何内容”陈述,参考Clive Evans的答案,我发现在Tomcat中,您可以使那些过于详细的错误信息从错误页面中消失而无需创建自定义ErrorReportValve。

您可以通过在“server.xml”上自定义ErrorReportValve的两个参数“showReport”和“showServerInfo”来实现此操作:

<Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false" />

官方文档链接

在Tomcat 7.0.55上有效,但在Tomcat 7.0.47上无效(我认为是由于下面链接中报告的问题:http://www.mail-archive.com/users@tomcat.apache.org/msg113856.html


7
与Tomcat 8一起工作。 十分出色的解决方案! - eis
3
Tomcat文档中提到,<Valve/>元素可以放置在server.xml文件的<Engine/>、<Host/>或<Context/>元素中。对我来说,在<Engine/>元素中放置阀门并没有起作用,但是在单独的<Host/>元素中放置它却可以。文档还说,Tomcat会将任何不匹配主机名的请求路由到在<Engine/>元素的defaultHost属性中设置的<Host/>元素上。要确保在您的主机/上下文之外的位置的URL不显示编入Tomcat的错误信息,您需要确保将此<Valve/>添加到默认的<Host/>中。 - Night Owl

13

停止Tomcat发送任何错误体的快速、略有些脏、但容易的方法是针对Tomcat主机调用setErrorReportValveClass,并使用自定义错误报告阀门来覆盖报告,使其不执行任何操作。例如:

public class SecureErrorReportValve extends ErrorReportValve {

@Override
protected void report(Request request,Response response,Throwable throwable) {
}

}

然后使用以下代码进行设置:

  ((StandardHost) tomcat.getHost()).setErrorReportValveClass(yourErrorValveClassName);
如果您想发送消息,并且认为Tomcat不应该干扰它,您需要类似以下的东西:
@Override
protected void report(final Request request, final Response response, final Throwable throwable) {
    String message = response.getMessage();
    if (message != null) {
        try {
            response.getWriter().print(message);
            response.finishResponse();
        } catch (IOException e) {
        }
    }
}

确实是更好的解决方案!谢谢。 - Poni
2
请随意指出规范的哪一部分表明您不应该使用Tomcat ErrorReportValve,因为它会将您的消息重写成包含许多您可能不想与外界分享的信息的消息,而是仅编写您的消息。我很感兴趣... - Clive Evans
1
另外,您可以编写自己的ErrorValve并在server.xml的<Host>元素中进行配置:<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" errorReportValveClass="security.MyErrorValveImpl">请参阅http://tomcat.apache.org/tomcat-7.0-doc/config/host.html - Erich Eichinger
现在你的应用程序依赖于Tomcat。 - mjs
1
如果你不使用Tomcat,你就不会遭受Tomcat泄露你的信息的痛苦... - Clive Evans
显示剩余3条评论

10

虽然它符合Servlet规范,但出于安全原因,我不希望Tomcat或任何其他Servlet容器发送错误详细信息。我也遇到了一些困难。经过搜索和尝试,解决方案可以总结如下:

  1. 正如其他人提到的那样,不要使用sendError(),而是使用setStatus()(在Jersey框架中您可以选择
  2. 像Spring Security这样的框架仍然使用sendError()...
  3. 编写一个Filter
    a. 将对sendError()的调用重定向到setStatus()
    b. 在最后刷新响应,以防止容器进一步修改响应

这里可以找到一个小例子Servlet filter


5
感谢您指出框架有预定义的行为并提供了一个示例过滤器,而且由于我正在使用Jersey,这让我发现了一个特定的Jersey属性。请参考该链接:https://jersey.java.net/apidocs/2.5/jersey/org/glassfish/jersey/server/ServerProperties.html#RESPONSE_SET_STATUS_OVER_SEND_ERROR - watery
1
@watery发布的链接已于2019年失效,正确的链接现在是https://jersey.github.io/apidocs/latest/jersey/org/glassfish/jersey/server/ServerProperties.html#RESPONSE_SET_STATUS_OVER_SEND_ERROR。 - maxxyme
链接又挂了,最好看看这个答案 ;) - bluish

9
如Heikki所说,设置状态而不是使用sendError()会导致Tomcat不接触响应实体/正文/有效载荷。
如果您只想发送响应标头而没有任何实体,就像我的情况一样,
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);

这个方法很妙。通过使用 Content-Length: 0,即使使用了 print(),也不会产生任何影响,例如:

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.getWriter().print("this string will be ignored due to the above line");

客户端会收到类似以下内容的信息:
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 0
Date: Wed, 28 Sep 2011 08:59:49 GMT

如果您想发送错误消息,请使用setContentLength()并指定消息长度(非零),或者您可以让服务器处理。

2
尽管这个问题有点老了,我也遇到了这个问题。首先,Tomcat的行为是绝对正确的。这是Servlet规范规定的。不应该违反规范改变Tomcat的行为。正如Heikki Vesalainen和mrCoder所提到的,只使用setStatussendError
如果有关心的人,我已经向Tomcat提出了一个ticket以改进sendError的文档。

1
你能澄清一下你所说的“绝对正确”的行为是什么吗?具体是哪个行为?Servlet规范的链接或摘录可能会有所帮助。我在3.0规范中找不到任何关于实际响应内容要求的信息,特别是针对错误响应的要求。就RFC2616而言,对于任何错误状态,都没有对实体内容的要求。 - Rob Hruska
当然可以。这与RFC无关,而是与Servlet规范本身相关。请查看Servlet规范3.0的第10.9.2章和10.9.3章,Oracle的Servlet API JavaDocs两个sendError方法以及我的问题Tomcat用户邮件列表 - Michael-O

2

配置<error-page>元素在web.xml

编辑$CATALINA_HOME/conf/web.xml,在末尾添加以下<error-page>,保存并重启Tomcat。

<web-app>

...
...
...

    <error-page>
        <error-code>404</error-code>
        <location>/404.html</location>
    </error-page>

    <error-page>
        <error-code>500</error-code>
        <location>/500.html</location>
    </error-page>

    <error-page>
        <error-code>400</error-code>
        <location>/400.html</location>
    </error-page>

</web-app>
  • 即使我没有为指定的location值(例如/400.html)创建有效的路由,它仍然像我预期的那样工作得很好。

之前

enter image description here

之后

enter image description here


1
你忽略了原帖中提到的一点,即他正在尝试避免在web.xml中为大量HTTP错误状态配置自定义错误页面。 - maxxyme
同意。对于那些通过谷歌搜索示例的人来说是有帮助的。这个问题具有很高的SEO评级。 - Jossef Harush Kadouri

1
为什么不直接使用空的HTML页面配置<error-page>元素呢?

2
我在我的问题中提到,我想避免为一堆状态配置错误页面(即使它们是空的)。如果这是我唯一的选择,那我想我必须这样做 - 但我正在寻找替代方案。 - Rob Hruska

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