是否应该在 HttpServletResponse.getOutputStream()/.getWriter() 上调用 .close()?

Posted

技术标签:

【中文标题】是否应该在 HttpServletResponse.getOutputStream()/.getWriter() 上调用 .close()?【英文标题】:Should one call .close() on HttpServletResponse.getOutputStream()/.getWriter()? 【发布时间】:2010-11-12 15:36:05 【问题描述】:

在 Java Servlet 中,可以通过response.getOutputStream()response.getWriter() 访问响应正文。写入此OutputStream 后是否应该调用.close()

一方面,布洛赫式的劝告总是关闭OutputStreams。另一方面,我不认为在这种情况下存在需要关闭的底层资源。套接字的打开/关闭在 HTTP 级别进行管理,以允许诸如持久连接之类的事情。

【问题讨论】:

不请您猜测是否存在要关闭的底层资源。如果 实施者 这么认为,或者更确切地说是知道,他将提供一个什么都不做的close()应该做的是关闭所有可关闭的资源。 即使您的代码没有打开它?我不这么认为... 【参考方案1】:

如果您将 Spring 与 Spring Security 结合使用,则不应关闭流或编写器。

ServletResponse.getOutputStream() 返回的流或从ServletResponse.getWriter() 返回的写入器将在关闭时提交响应。提交响应,如explained here,意味着 http 状态和标头变得不可变,即使在处理此请求期间抛出异常,Spring 框架也无法调整 http 状态。

OnCommittedResponseWrapper 类的实例用作ServletResponse 的实现,这是负责此行为的code(也请检查javadoc)。

考虑以下示例控制器:

@RestController
public class MyController 
    @RequestMapping(method = RequestMethod.POST, value = "/blah")
    public void entrypoint(ServletRequest request, ServletResponse response) throws IOException 
        try (var writer = response.getWriter()) 
            throw new RuntimeException("Something bad happened here");
        
    

当抛出异常时,首先会调用writer.close(),这会将响应http状态冻结为其默认值200

只有在那之后,异常才会开始从这个控制器传播到 Spring 错误处理程序。 Spring 错误处理程序将无法将状态更改为 500,因为响应已提交,因此状态将保持为 200

【讨论】:

【参考方案2】:

通常您不应关闭流。作为 servlet 请求生命周期的一部分,servlet 容器将在 servlet 完成运行后自动关闭流。

例如,如果您关闭了流,如果您实现了Filter,它将不可用。

说了这么多,如果你关闭它,只要你不尝试再次使用它,什么都不会发生。

编辑:another filter link

EDIT2:adrian.tarau 是正确的,如果你想在 servlet 完成它的事情后改变响应,你应该创建一个扩展 HttpServletResponseWrapper 的包装器并缓冲输出。这是为了防止输出直接发送到客户端,但也允许您保护 servlet 是否关闭流,根据这段摘录(强调我的):

修改响应的过滤器必须 通常捕获它之前的响应 被退回给客户。的方式 这样做是为了传递 servlet 生成响应 溪流。替代流防止 servlet 从关闭原始 完成时的响应流和 允许过滤器修改 servlet 的响应。

Article

从那篇 Sun 官方文章中可以推断,从 servlet 中关闭 OutputStream 是正常现象,但不是强制性的。

【讨论】:

这是正确的。需要注意的一点是,在某些情况下,您可能需要刷新流,这是完全允许的。 关闭 writer 还有另一个副作用。 response.setStatus 关闭后也无法设置状态码。 遵循此建议。它将为您节省很多痛苦。除非您知道为什么要这样做,否则我也不会 flush() - 您应该让容器处理缓冲。【参考方案3】:

反对关闭OutputStream 的另一个论点。看看这个 servlet。它抛出一个异常。异常在 web.xml 中映射到错误 JSP:

package ser;

import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet(name = "Erroneous", urlPatterns = "/Erroneous")
public class Erroneous extends HttpServlet 

  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
    resp.setContentType("text/html;charset=UTF-8");
    PrintWriter out = resp.getWriter();
    try 
      throw new IOException("An error");
     finally 
//      out.close();
    
  

web.xml 文件包含:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <error-page>
        <exception-type>java.io.IOException</exception-type>
        <location>/error.jsp</location>
    </error-page>
</web-app>

还有error.jsp:

<%@page contentType="text/html" pageEncoding="UTF-8" isErrorPage="true"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Error Page</title>
    </head>
    <body>
        <h1><%= exception.getMessage()%></h1>
    </body>
</html>

当您在浏览器中加载/Erroneous 时,您会看到显示“错误”的错误页面。 但是,如果您取消注释上述 servlet 中的 out.close() 行,重新部署应用程序并重新加载 /Erroneous,您将在浏览器中看不到任何内容。我不知道实际发生了什么,但我猜out.close() 阻止了错误处理。

使用 Tomcat 7.0.50 测试,Java EE 6 使用 Netbeans 7.4。

【讨论】:

【参考方案4】:

如果有可能在“包含”资源上调用过滤器,您绝对应该关闭流。这将导致包含资源失败并出现“流关闭”异常。

【讨论】:

感谢您添加此评论。有趣的是,我仍然在努力解决这个问题——刚才我注意到 NetBeans 的 servlet 模板确实包含关闭输出流的代码......【参考方案5】:

您应该关闭流,代码更简洁,因为您调用 getOutputStream() 并且流没有作为参数传递给您,而通常您只是使用它而不尝试关闭它。 Servlet API 没有说明如果输出流可以关闭或不能关闭,在这种情况下您可以安全地关闭流,如果 servlet 没有关闭流,任何容器都会负责关闭流。

这是 Jetty 中的 close() 方法,如果流没有关闭,它们会关闭它。

public void close() throws IOException
    
        if (_closed)
            return;

        if (!isIncluding() && !_generator.isCommitted())
            commitResponse(HttpGenerator.LAST);
        else
            flushResponse();

        super.close();
    

同样作为过滤器的开发者,你不应该假设 OutputStream 没有关闭,如果你想在 servlet 完成它的工作之后改变内容,你应该总是传递另一个 OutputStream。

编辑:我总是关闭流,我对 Tomcat/Jetty 没有任何问题。我认为您应该对任何新旧容器都没有任何问题。

【讨论】:

“你应该关闭流,代码更干净......” - 对我来说,使用 .close() 的代码看起来不如没有的代码干净,特别是如果 .close() 是不必要的-- 这就是这个问题试图确定的。 是的,但美在后面 :) 无论如何,由于 API 不清楚,我宁愿关闭它,代码看起来一致,一旦您请求 OutputStream,您应该关闭它,除非 API 显示“不要关闭它”。 我不认为在我自己的代码中关闭输出流是一个好习惯。这就是容器的工作。 get* 不会创建流​​,它不是生产者。你不应该关闭它,容器必须这样做。【参考方案6】:

它们的一般规则是:如果您打开了流,那么您应该关闭它。如果你没有,你不应该。确保代码是对称的。

HttpServletResponse 的情况下,它有点不太明确,因为调用getOutputStream() 是否是打开流的操作并不明显。 Javadoc 只是说它“Returns a ServletOutputStream”; getWriter() 也是如此。无论哪种方式,很明显HttpServletResponse“拥有”流/写入器,它(或容器)负责再次关闭它。

所以回答你的问题 - 不,在这种情况下你不应该关闭流。容器必须这样做,如果您在它之前进入容器,您可能会在应用程序中引入细微的错误。

【讨论】:

我同意这个答案,您可能还想查看 ServletResponse.flushBuffer() 参见:docs.oracle.com/javaee/1.4/api/javax/servlet/… “如果你打开了流,那么你应该关闭它。如果你没有,你不应该”——说得好 感觉就像学校董事会上贴的句子“打开就关闭,打开就关闭。解锁就锁定。[... ]" 我注意到我在代码的早期使用了流并且再也没有,客户端等待整个 servlet 执行,但是当我调用 close() 时,当我完成流时,客户端立即返回,servlet 的其余部分继续执行。那不是让答案更具相对性吗?而不是明确的是或否 "如果你打开了流,那么你应该关闭它。如果你没有,你不应该。确保代码是对称的。" - 那么如果你创建另一个包装这个流的流呢?在这种情况下很难保持对称,因为调用关闭外部流通常会关闭嵌套的流。

以上是关于是否应该在 HttpServletResponse.getOutputStream()/.getWriter() 上调用 .close()?的主要内容,如果未能解决你的问题,请参考以下文章

我应该关闭 servlet 输出流吗? [复制]

HttpServletResponse sendRedirect 永久

HttpServletResponse

HttpServletResponse

HttpServletResponse

HttpServletResponse