是否应该在 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()
?
一方面,布洛赫式的劝告总是关闭OutputStream
s。另一方面,我不认为在这种情况下存在需要关闭的底层资源。套接字的打开/关闭在 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()?的主要内容,如果未能解决你的问题,请参考以下文章