Deve-se chamar .close () em HttpServletResponse.getOutputStream () /. GetWriter ()?

96

Em Java Servlets, pode-se acessar o corpo da resposta via response.getOutputStream()ou response.getWriter(). Deve-se invocar .close()isso OutputStreamdepois de ter sido escrito?

Por um lado, existe a exortação blochiana de sempre fechar OutputStreams. Por outro lado, não acho que neste caso haja um recurso subjacente que precise ser fechado. A abertura / fechamento de sockets é gerenciado no nível HTTP, para permitir coisas como conexões persistentes e tal.

Steven Huwig
fonte
Você não é convidado a adivinhar se há um recurso subjacente a ser fechado. Se o implementador pensa assim, ou melhor sabe, ele fornecerá um close()que não faz nada. O que você deve fazer é fechar todos os recursos que podem ser fechados.
Marquês de Lorne
1
Mesmo que seu código não o tenha aberto? Acho que não ...
Steven Huwig

Respostas:

93

Normalmente você não deve fechar o fluxo. O contêiner de servlet fechará automaticamente o fluxo após a conclusão da execução do servlet como parte do ciclo de vida da solicitação do servlet.

Por exemplo, se você fechasse o fluxo, ele não estaria disponível se você implementasse um Filtro .

Dito isso, se você o fechar, nada de ruim acontecerá, desde que você não tente usá-lo novamente.

EDIT: outro link de filtro

EDIT2: adrian.tarau está correto em que se você quiser alterar a resposta após o servlet ter feito seu trabalho, você deve criar um wrapper estendendo HttpServletResponseWrapper e bufferizar a saída. Isso evita que a saída vá diretamente para o cliente, mas também permite que você proteja se o servlet fecha o fluxo, conforme este trecho (grifo meu):

Um filtro que modifica uma resposta geralmente deve capturar a resposta antes que ela seja devolvida ao cliente. A maneira de fazer isso é passar o servlet que gera a resposta um fluxo stand-in. O fluxo substituto evita que o servlet feche o fluxo de resposta original ao ser concluído e permite que o filtro modifique a resposta do servlet.

Artigo

Pode-se inferir desse artigo oficial da Sun que o fechamento OutputStreamde um servlet é algo normal, mas não obrigatório.

Nemi
fonte
2
Isto está certo. Uma coisa a se notar é que em alguns casos você pode precisar liberar o fluxo, e isso é perfeitamente permitido.
toluju
1
Existe outro efeito colateral de fechar o escritor. Você também não poderá definir o código de status por meio de response.setStatus após ter sido fechado.
che javara de
1
Siga este conselho. Isso vai te poupar muita dor. Eu não flush () a menos que você saiba por que está fazendo isso - você deve deixar o contêiner lidar com o buffer.
Hal50000,
75

A regra geral deles é esta: se você abriu o fluxo, deve fechá-lo. Do contrário, não deveria. Certifique-se de que o código seja simétrico.

No caso de HttpServletResponse, é um pouco menos claro, já que não é óbvio se a chamada getOutputStream()é uma operação que abre o fluxo. O Javadoc apenas diz que " Returns a ServletOutputStream"; da mesma forma para getWriter(). De qualquer forma, o que está claro é que HttpServletResponse"possui" o stream / gravador, e ele (ou o contêiner) é responsável por fechá-lo novamente.

Portanto, para responder à sua pergunta - não, você não deve fechar o fluxo neste caso. O contêiner deve fazer isso e, se você entrar lá antes, corre o risco de introduzir bugs sutis em seu aplicativo.

skaffman
fonte
Eu concordo com esta resposta, você também pode dar uma olhada no ServletResponse.flushBuffer () veja: docs.oracle.com/javaee/1.4/api/javax/servlet/…
cyber-monge
14
"se você abriu o riacho, deve fechá-lo. Se não abriu, não deveria" - bem dito
Srikanth Reddy Lingala
2
Parece as frases postadas no quadro da escola "se você abrir, feche. Se você ligar, desligue. Se você desbloquear, tranque. [...]"
Ricardo
Iv notou que eu uso o stream no início do meu código e nunca mais, o cliente espera que todo o servlet seja executado, mas quando eu chamo close()quando termino o stream, o cliente retorna imediatamente e o resto do servlet continua em execução. Isso não torna a resposta um pouco mais relativa? em oposição a um sim ou não definitivo
BiGGZ
"se você abriu o fluxo, deve fechá-lo. Se não abriu, não deve. Certifique-se de que o código seja simétrico." - E se você criar outro fluxo que envolva este fluxo? É difícil permanecer simétrico neste caso, uma vez que chamar o fluxo externo fechará normalmente o aninhado.
steinybot
5

Se houver alguma chance de o filtro ser chamado em um recurso 'incluído', você definitivamente não deve fechar o fluxo. Isso fará com que o recurso de inclusão falhe com uma exceção 'fluxo fechado'.

Skip Head
fonte
Obrigado por adicionar este comentário. Hilariante o suficiente, ainda estou lutando um pouco com o problema - agora mesmo percebi que o modelo de servlet do NetBeans inclui código para fechar o fluxo de saída ...
Steven Huwig
4

Você deve fechar o fluxo, o código é mais limpo, pois você invoca getOutputStream () e o fluxo não é passado para você como um parâmetro, quando normalmente você apenas o usa e não tenta fechá-lo. A API do Servlet não afirma que se o fluxo de saída pode ser fechado ou não deve ser fechado, neste caso você pode fechar o fluxo com segurança, qualquer contêiner lá fora cuida de fechar o fluxo se ele não foi fechado pelo servlet.

Aqui está o método close () no Jetty, eles fecham o fluxo se não for fechado.

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

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

        super.close();
    }

Além disso, como desenvolvedor de um Filtro, você não deve presumir que o OutputStream não está fechado, você sempre deve passar outro OutputStream se quiser alterar o conteúdo após o servlet ter feito seu trabalho.

EDIT: Estou sempre fechando o stream e não tive problemas com o Tomcat / Jetty. Não acho que você deva ter problemas com qualquer container, antigo ou novo.

adrian.tarau
fonte
4
"Você deve fechar o stream, o código está mais limpo ..." - Para mim, o código salpicado com .close () parece menos limpo do que o código sem, principalmente se o .close () for desnecessário - que é esta questão tentando determinar.
Steven Huwig
1
Sim, mas a beleza vem depois :) De qualquer forma, como a API não está clara, prefiro fechá-la, o código parece consistente, uma vez que você solicitar um OutputStream você deve fechá-lo, a menos que a API diga "não feche".
adrian.tarau
Não acho que fechar o fluxo de saída em meu próprio código seja uma boa prática. Esse é o trabalho do contêiner.
JimHawkins
get * não cria o fluxo, não é seu produtor. Você não deve fechá-lo, o container deve fazê-lo.
dmatej
3

Outro argumento contra o fechamento do OutputStream. Olhe para este servlet. Ele lança uma exceção. A exceção é mapeada no web.xml para um erro 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();
    }
  }
}

O arquivo web.xml contém:

<?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>

E o 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>

Ao carregar /Erroneousno navegador, você vê a página de erro exibindo "Um erro". Mas se você remover o comentário da out.close()linha no servlet acima, reimplantar o aplicativo e recarregar, /Erroneousvocê não verá nada no navegador. Não tenho ideia do que realmente está acontecendo, mas acho que isso out.close()impede o tratamento de erros.

Testado com Tomcat 7.0.50, Java EE 6 usando Netbeans 7.4.

user1872904
fonte