Нужно ли вызывать.close() для HttpServletResponse.getOutputStream()/. GetWriter()?

Я не мог найти авторитетный ответ на это с некоторым Google. В Java-сервлетах можно получить доступ к телу ответа через response.getOutputStream() или response.getWriter(). Нужно ли вызывать.close() для этого потока после того, как он был записан?

С одной стороны, есть призыв Блохиана всегда закрывать выходные потоки. С другой стороны, я не думаю, что в этом случае есть основной ресурс, который должен быть закрыт. Открытие / закрытие сокетов управляется на уровне HTTP, чтобы разрешить такие вещи, как постоянные соединения и тому подобное.

6 ответов

Решение

Обычно вы не должны закрывать поток. Контейнер сервлета автоматически закроет поток после завершения работы сервлета в рамках жизненного цикла запроса сервлета.

Например, если вы закрыли поток, он не будет доступен, если вы внедрили фильтр.

Сказав все это, если вы закроете его, ничего плохого не произойдет, если вы не попытаетесь использовать его снова.

РЕДАКТИРОВАТЬ: еще одна ссылка фильтра

EDIT2: adrian.tarau правильно в том, что если вы хотите изменить ответ после того, как сервлет сделал свое дело, вы должны создать оболочку, расширяющую HttpServletResponseWrapper, и буферизировать вывод. Это позволяет предотвратить вывод выходных данных непосредственно клиенту, но также позволяет вам защитить, если сервлет закрывает поток, в соответствии с этим отрывком (выделено мной):

Фильтр, который изменяет ответ, должен обычно захватывать ответ, прежде чем он будет возвращен клиенту. Способ сделать это - передать сервлет, генерирующий ответ, в резервный поток. Резервный поток не позволяет сервлету закрывать исходный поток ответов после его завершения и позволяет фильтру изменять ответ сервлета.

Статья

Из этой официальной статьи Sun можно сделать вывод, что закрытие выходного потока из сервлета является обычным явлением, но не обязательным.

Общее правило их таково: если вы открыли поток, то вы должны закрыть его. Если вы этого не сделали, вы не должны. Убедитесь, что код симметричный.

В случае HttpServletResponse, это немного менее ясно, так как это не очевидно, если вызов getOutputStream() это операция, которая открывает поток. Javadoc просто говорит, что этоReturns a ServletOutputStream"; аналогично для getWriter(), В любом случае, ясно, что HttpServletResponse "владеет" потоком / устройством записи, и он (или контейнер) отвечает за его повторное закрытие.

Поэтому, чтобы ответить на ваш вопрос - нет, вам не следует закрывать поток в этом случае. Контейнер должен сделать это, и если вы попадете туда раньше, вы рискуете внести незначительные ошибки в ваше приложение.

Если есть вероятность, что фильтр может быть вызван на "включенном" ресурсе, вам определенно не следует закрывать поток. Это приведет к сбою включающего ресурса с исключением "поток закрыт".

Еще один аргумент против закрытия OutputStream, Посмотри на этот сервлет. Это исключение. Исключение сопоставляется в файле 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>

И ошибка.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 в браузере вы видите страницу с сообщением об ошибке "Ошибка". Но если вы откомментируете out.close() в приведенном выше сервлете, повторно разверните приложение и перезагрузите /Erroneous вы ничего не увидите в браузере. Я понятия не имею, что на самом деле происходит, но я думаю, что out.close() предотвращает обработку ошибок.

Протестировано с Tomcat 7.0.50, Java EE 6 с использованием Netbeans 7.4.

Вы должны закрыть поток, код будет чище, так как вы вызываете getOutputStream(), и поток не передается вам в качестве параметра, когда обычно вы просто используете его и не пытаетесь закрыть его. Servlet API не утверждает, что если выходной поток может быть закрыт или не должен быть закрыт, в этом случае вы можете безопасно закрыть поток, любой контейнер позаботится о закрытии потока, если он не был закрыт сервлетом.

Вот метод close() в Jetty, они закрывают поток, если он не закрыт.

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

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

        super.close();
    }

Также как разработчик фильтра вы не должны предполагать, что OutputStream не закрыт, вы всегда должны передавать другой OutputStream, если хотите изменить содержимое после того, как сервлет выполнил свою работу.

РЕДАКТИРОВАТЬ: Я всегда закрываю поток, и у меня не было никаких проблем с Tomcat/Jetty. Я не думаю, что у вас должны быть какие-либо проблемы с любым контейнером, старым или новым.

Если вы используете Spring с Spring Security, вам не следует закрывать поток или модуль записи.

Поток вернулся из ServletResponse.getOutputStream()или писатель вернулся из ServletResponse.getWriter()зафиксирует ответ после закрытия. Фиксация ответа, как описано здесь , означает, что статус http и заголовки становятся неизменяемыми, и среда Spring не сможет настроить статус http, даже если во время обслуживания этого запроса возникнет исключение.

Экземпляр OnCommittedResponseWrapperкласс используется как реализация ServletResponseи вот код , отвечающий за это поведение (также проверьте 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 ответа до его значения по умолчанию.

Только после этого исключение начнет распространяться с этого контроллера на обработчики ошибок Spring. Обработчики ошибок Spring не смогут изменить статус на 500потому что ответ уже был зафиксирован, и поэтому статус останется 200.

Другие вопросы по тегам