Czy należy wywołać .close () na HttpServletResponse.getOutputStream () /. GetWriter ()?

96

W Java Servlets można uzyskać dostęp do treści odpowiedzi za pośrednictwem response.getOutputStream()lub response.getWriter(). Czy powinno się to wzywać .close()po tym, OutputStreamjak zostało napisane?

Z jednej strony istnieje zachęta Blocha, aby zawsze zamykać OutputStreams. Z drugiej strony nie sądzę, że w tym przypadku istnieje podstawowy zasób, który należy zamknąć. Otwieranie / zamykanie gniazd jest zarządzane na poziomie HTTP, aby umożliwić takie rzeczy, jak trwałe połączenia i tym podobne.

Steven Huwig
źródło
Nie możesz zgadywać, czy istnieje podstawowy zasób do zamknięcia. Jeśli osoba wdrażająca tak myśli, a raczej wie, zapewni to close(), co nic nie robi. Co ty powinien zrobić jest blisko każdego zasobu zamykany.
Markiz Lorne
1
Nawet jeśli twój kod go nie otworzył? Nie sądzę ...
Steven Huwig

Odpowiedzi:

93

Zwykle nie należy zamykać strumienia. Kontener serwletów automatycznie zamknie strumień po zakończeniu działania serwletu w ramach cyklu życia żądania serwletu.

Na przykład, jeśli zamkniesz strumień, nie będzie on dostępny, jeśli zaimplementujesz filtr .

Powiedziawszy to wszystko, jeśli go zamkniesz, nic złego się nie stanie, o ile nie spróbujesz go ponownie użyć.

EDYCJA: kolejny link do filtra

EDIT2: adrian.tarau jest poprawny w tym sensie, że jeśli chcesz zmienić odpowiedź po tym, jak aplet wykonał swoje zadanie, powinieneś utworzyć opakowanie rozszerzające HttpServletResponseWrapper i buforować wyjście. Ma to na celu zapobieganie wysyłaniu danych wyjściowych bezpośrednio do klienta, ale także pozwala chronić, jeśli serwlet zamknie strumień, zgodnie z tym fragmentem (moje wyróżnienie):

Filtr modyfikujący odpowiedź musi zwykle przechwytywać odpowiedź, zanim zostanie ona zwrócona klientowi. Sposobem na to jest przekazanie serwletowi, który generuje odpowiedź, strumienia zastępczego. Strumień rezerwowy zapobiega zamknięciu przez serwlet pierwotnego strumienia odpowiedzi po jego zakończeniu i umożliwia filtrowi modyfikację odpowiedzi serwletu.

Artykuł

Z oficjalnego artykułu firmy Sun można wywnioskować, że zamknięcie OutputStreamserwletu jest czymś normalnym, ale nie jest obowiązkowe.

Nemi
źródło
2
To jest poprawne. Należy zauważyć, że w niektórych przypadkach może być konieczne przepłukanie strumienia, a to jest całkowicie dopuszczalne.
toluju
1
Jest jeszcze jeden efekt uboczny zamknięcia pisarza. Nie będzie też można ustawić kodu statusu poprzez response.setStatus po jej zamknięciu.
che javara
1
Postępuj zgodnie z tą radą. Zaoszczędzi ci to wiele bólu. Nie użyłbym też flush (), chyba że wiesz, dlaczego to robisz - powinieneś pozwolić kontenerowi obsługiwać buforowanie.
Hal50000
76

Ogólna zasada jest taka: jeśli otworzyłeś strumień, powinieneś go zamknąć. Jeśli nie, nie powinieneś. Upewnij się, że kod jest symetryczny.

W przypadku HttpServletResponsejest to trochę mniej wyraźne cięcie, ponieważ nie jest oczywiste, czy wywołanie getOutputStream()jest operacją, która otwiera strumień. Javadoc po prostu mówi, że to " Returns a ServletOutputStream"; podobnie dla getWriter(). Tak czy inaczej, jasne jest, że HttpServletResponse„jest właścicielem” strumienia / modułu zapisującego i to on (lub kontener) jest odpowiedzialny za jego ponowne zamknięcie.

A więc odpowiadając na twoje pytanie - nie, w tym przypadku nie powinieneś zamykać strumienia. Kontener musi to zrobić, a jeśli dostaniesz się tam wcześniej, ryzykujesz wprowadzenie subtelnych błędów do aplikacji.

skaffman
źródło
Zgadzam się z tą odpowiedzią, możesz również chcieć sprawdzić ServletResponse.flushBuffer () patrz: docs.oracle.com/javaee/1.4/api/javax/servlet/…
cyber-monk
14
„Jeśli otworzyłeś strumień, powinieneś go zamknąć. Jeśli tego nie zrobiłeś, nie powinieneś” - dobrze powiedziane
Srikanth Reddy Lingala
2
Wydaje się, że zdania na tablicy szkolnej „jeśli ją otworzysz, zamknij. Jeśli ją włączysz, wyłącz. Jeśli ją odblokujesz, zablokuj ją. [...]”
Ricardo
Iv zauważył, że jeśli używam strumienia na początku mojego kodu i nigdy więcej, klient czeka na wykonanie całego serwletu, ale kiedy wywołuję, close()gdy skończyłem ze strumieniem, klient natychmiast wraca, a reszta serwletu kontynuuje wykonywanie. Czy to nie sprawia, że ​​odpowiedź jest nieco bardziej względna? w przeciwieństwie do ostatecznego tak lub nie
BiGGZ
„Jeśli otworzyłeś strumień, powinieneś go zamknąć. Jeśli tego nie zrobiłeś, nie powinieneś. Upewnij się, że kod jest symetryczny”. - A co z tego, że utworzysz kolejny strumień, który będzie zawierał ten strumień? Trudno jest zachować symetrię w tym przypadku, ponieważ wywołanie zamknięcia zewnętrznego strumienia zazwyczaj zamyka strumień zagnieżdżony.
steinybot
5

Jeśli istnieje jakakolwiek szansa, że ​​filtr może zostać wywołany na zasobie „uwzględnionym”, zdecydowanie nie należy zamykać strumienia. Spowoduje to niepowodzenie dołączania zasobu i wyjątek „strumień zamknięty”.

Skip Head
źródło
Dzięki za dodanie tego komentarza. Co zabawne, wciąż trochę
zmagam się
4

Powinieneś zamknąć strumień, kod jest czystszy, ponieważ wywołujesz getOutputStream (), a strumień nie jest przekazywany do ciebie jako parametr, kiedy zwykle po prostu go używasz i nie próbujesz go zamknąć. Servlet API nie stwierdza, że ​​jeśli strumień wyjściowy może być zamknięty lub nie może być zamknięty, w tym przypadku możesz bezpiecznie zamknąć strumień, każdy kontener na zewnątrz dba o zamknięcie strumienia, jeśli nie został zamknięty przez serwlet.

Oto metoda close () w Jetty, zamykają strumień, jeśli nie jest zamknięty.

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

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

        super.close();
    }

Również jako twórca filtru nie powinieneś zakładać, że strumień OutputStream nie jest zamknięty. Zawsze powinieneś przekazywać inny strumień wyjściowy, jeśli chcesz zmienić zawartość po wykonaniu przez serwlet swojej pracy.

EDYCJA: Zawsze zamykam strumień i nie miałem żadnych problemów z Tomcat / Jetty. Myślę, że nie powinieneś mieć problemów z jakimkolwiek kontenerem, starym lub nowym.

adrian.tarau
źródło
4
„Powinieneś zamknąć strumień, kod jest czystszy…” - Dla mnie kod usiany .close () wygląda mniej czysto niż kod bez, zwłaszcza jeśli .close () jest niepotrzebny - o to właśnie chodzi w tym pytaniu próbując ustalić.
Steven Huwig
1
tak, ale piękno przychodzi później :) W każdym razie, ponieważ API nie jest jasne, wolałbym je zamknąć, kod wygląda spójnie, po zażądaniu OutputStream należy go zamknąć, chyba że API mówi „nie zamykaj go”.
adrian.tarau
Nie sądzę, aby zamykanie strumienia wyjściowego we własnym kodzie było dobrą praktyką. To zadanie pojemnika.
JimHawkins
get * nie tworzy strumienia, nie jest jego producentem. Nie powinieneś go zamykać, pojemnik musi to zrobić.
dmatej
3

Kolejny argument przeciwko zamykaniu OutputStream. Spójrz na ten serwlet. Rzuca wyjątek. Wyjątek jest mapowany w pliku web.xml na błąd 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();
    }
  }
}

Plik web.xml zawiera:

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

A błąd.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>

Po załadowaniu /Erroneousw przeglądarce zobaczysz stronę błędu z komunikatem „Błąd”. Ale jeśli usuniesz komentarz z out.close()linii w powyższym serwlecie, ponownie wdrożysz aplikację i przeładujesz /Erroneous, nic nie zobaczysz w przeglądarce. Nie mam pojęcia, co się właściwie dzieje, ale myślę, że to out.close()uniemożliwia obsługę błędów.

Testowane z Tomcat 7.0.50, Java EE 6 przy użyciu Netbeans 7.4.

user1872904
źródło