Sollte man .close () unter HttpServletResponse.getOutputStream () /. GetWriter () aufrufen?

96

In Java-Servlets kann über response.getOutputStream()oder auf den Antworttext zugegriffen werden response.getWriter(). Sollte ein Anruf .close()auf das , OutputStreamnachdem es wurde geschrieben?

Einerseits gibt es die blochianische Ermahnung, OutputStreams immer zu schließen . Andererseits glaube ich nicht, dass in diesem Fall eine zugrunde liegende Ressource geschlossen werden muss. Das Öffnen / Schließen von Sockets wird auf HTTP-Ebene verwaltet, um beispielsweise dauerhafte Verbindungen und dergleichen zu ermöglichen.

Steven Huwig
quelle
Sie werden nicht aufgefordert, zu erraten, ob eine zugrunde liegende Ressource geschlossen werden muss. Wenn der Implementierer dies glaubt oder eher weiß, wird er eine bereitstellen close(), die nichts tut. Was Sie tun sollten, ist jede verschließbare Ressource zu schließen.
Marquis von Lorne
1
Auch wenn Ihr Code es nicht geöffnet hat? Ich glaube nicht ...
Steven Huwig

Antworten:

93

Normalerweise sollten Sie den Stream nicht schließen. Der Servlet-Container schließt den Stream automatisch, nachdem das Servlet als Teil des Servlet-Anforderungslebenszyklus ausgeführt wurde.

Wenn Sie beispielsweise den Stream geschlossen haben, ist er nicht verfügbar, wenn Sie einen Filter implementiert haben .

Trotzdem passiert nichts Schlimmes, wenn Sie es schließen, solange Sie nicht versuchen, es erneut zu verwenden.

BEARBEITEN: ein weiterer Filterlink

EDIT2: adrian.tarau ist insofern richtig, als wenn Sie die Antwort ändern möchten, nachdem das Servlet seine Aufgabe erfüllt hat, sollten Sie einen Wrapper erstellen, der HttpServletResponseWrapper erweitert, und die Ausgabe puffern. Dies soll verhindern, dass die Ausgabe direkt an den Client gesendet wird, ermöglicht Ihnen jedoch auch den Schutz, wenn das Servlet den Stream schließt, gemäß diesem Auszug (Hervorhebung von mir):

Ein Filter, der eine Antwort ändert, muss normalerweise die Antwort erfassen, bevor sie an den Client zurückgegeben wird. Der Weg, dies zu tun, besteht darin, das Servlet, das die Antwort generiert, als Ersatzstrom zu übergeben. Der Stand-In-Stream verhindert, dass das Servlet den ursprünglichen Antwortstrom schließt, wenn er abgeschlossen ist, und ermöglicht es dem Filter, die Antwort des Servlets zu ändern.

Artikel

Aus diesem offiziellen Sun-Artikel kann man schließen, dass das Schließen OutputStreameines Servlets etwas Normales ist, aber nicht obligatorisch.

Nemi
quelle
2
Das ist richtig. Eine Sache zu beachten ist, dass Sie in einigen Fällen möglicherweise den Stream spülen müssen, und das ist vollkommen zulässig.
Toluju
1
Es gibt einen weiteren Nebeneffekt beim Schließen des Schreibers. Sie können den Statuscode auch nicht über response.setStatus festlegen, nachdem er geschlossen wurde.
Che Javara
1
Befolgen Sie diesen Rat. Es wird Ihnen viel Schmerz ersparen. Ich würde auch nicht spülen (), wenn Sie nicht wissen, warum Sie es tun - Sie sollten den Container die Pufferung übernehmen lassen.
Hal50000
75

Die allgemeine Regel lautet: Wenn Sie den Stream geöffnet haben, sollten Sie ihn schließen. Wenn Sie nicht, sollten Sie nicht. Stellen Sie sicher, dass der Code symmetrisch ist.

Im Fall von HttpServletResponseist es etwas weniger eindeutig, da es nicht offensichtlich ist, ob der Aufruf getOutputStream()eine Operation ist, die den Stream öffnet. Der Javadoc sagt nur, dass es " Returns a ServletOutputStream"; ähnlich für getWriter(). In jedem Fall ist klar, dass HttpServletResponseder Stream / Writer "gehört" und er (oder der Container) dafür verantwortlich ist, ihn wieder zu schließen.

Um Ihre Frage zu beantworten - nein, Sie sollten den Stream in diesem Fall nicht schließen. Der Container muss dies tun, und wenn Sie zuvor dort hineinkommen, besteht die Gefahr, dass Ihre Anwendung subtile Fehler enthält.

Skaffman
quelle
Ich stimme dieser Antwort zu. Vielleicht möchten Sie auch den ServletResponse.flushBuffer () auschecken, siehe: docs.oracle.com/javaee/1.4/api/javax/servlet/…
Cyber-Monk
14
"Wenn Sie den Stream geöffnet haben, sollten Sie ihn schließen. Wenn Sie dies nicht getan haben, sollten Sie es nicht tun" - gut gesagt
Srikanth Reddy Lingala
2
Es fühlt sich an wie die Sätze auf der Schulbehörde: "Wenn Sie es öffnen, schließen Sie es. Wenn Sie es einschalten, schalten Sie es aus. Wenn Sie es entsperren, sperren Sie es. [...]"
Ricardo
Ich habe bemerkt, dass ich den Stream früh in meinem Code verwende und nie wieder, der Client wartet auf die Ausführung des gesamten Servlets, aber wenn ich aufrufe, wenn ich close()mit dem Stream fertig bin, kehrt der Client sofort zurück und der Rest des Servlets wird weiter ausgeführt. Macht das die Antwort dann nicht etwas relativer? im Gegensatz zu einem definitiven Ja oder Nein
BiGGZ
"Wenn Sie den Stream geöffnet haben, sollten Sie ihn schließen. Wenn Sie dies nicht getan haben, sollten Sie es nicht tun. Stellen Sie sicher, dass der Code symmetrisch ist." - Was ist, wenn Sie dann einen anderen Stream erstellen, der diesen Stream umschließt? In diesem Fall ist es schwierig, symmetrisch zu bleiben, da das Aufrufen des äußeren Streams normalerweise den verschachtelten Stream schließt.
Steinybot
5

Wenn die Möglichkeit besteht, dass der Filter für eine "eingeschlossene" Ressource aufgerufen wird, sollten Sie den Stream auf keinen Fall schließen. Dies führt dazu, dass die eingeschlossene Ressource mit einer Ausnahme "Stream geschlossen" fehlschlägt.

Kopf überspringen
quelle
Vielen Dank für das Hinzufügen dieses Kommentars. Komischerweise beschäftige ich mich immer noch ein wenig mit dem Problem - gerade jetzt habe ich festgestellt, dass die Servlet-Vorlage von NetBeans Code zum Schließen des Ausgabestreams enthält ...
Steven Huwig
4

Sie sollten den Stream schließen, der Code ist sauberer, da Sie getOutputStream () aufrufen und der Stream nicht als Parameter an Sie übergeben wird, wenn Sie ihn normalerweise nur verwenden und nicht versuchen, ihn zu schließen. Die Servlet-API gibt nicht an, dass, wenn der Ausgabestream geschlossen werden kann oder nicht geschlossen werden darf. In diesem Fall können Sie den Stream sicher schließen, jeder Container da draußen dafür sorgt, dass der Stream geschlossen wird, wenn er nicht vom Servlet geschlossen wurde.

Hier ist die Methode close () in Jetty. Sie schließen den Stream, wenn er nicht geschlossen wird.

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

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

        super.close();
    }

Auch als Entwickler eines Filters sollten Sie nicht davon ausgehen, dass der OutputStream nicht geschlossen ist. Sie sollten immer einen anderen OutputStream übergeben, wenn Sie den Inhalt ändern möchten, nachdem das Servlet seine Arbeit erledigt hat.

EDIT: Ich schließe immer den Stream und hatte keine Probleme mit Tomcat / Jetty. Ich denke nicht, dass Sie Probleme mit alten oder neuen Containern haben sollten.

adrian.tarau
quelle
4
"Sie sollten den Stream schließen, der Code ist sauberer ..." - Für mich sieht mit .close () gespickter Code weniger sauber aus als Code ohne .close (), insbesondere wenn .close () nicht erforderlich ist - genau das ist diese Frage versuchen zu bestimmen.
Steven Huwig
1
Ja, aber Schönheit kommt danach :) Da die API nicht klar ist, würde ich sie lieber schließen, sieht der Code konsistent aus. Sobald Sie einen OutputStream anfordern, sollten Sie ihn schließen, es sei denn, die API sagt "Nicht schließen".
adrian.tarau
Ich halte es nicht für eine gute Praxis, den Ausgabestream in meinem eigenen Code zu schließen. Das ist die Aufgabe des Containers.
Jim Hawkins
get * erstellt den Stream nicht, es ist nicht der Produzent. Sie sollten es nicht schließen, der Container muss es tun.
Dmatej
3

Ein weiteres Argument gegen die Schließung der OutputStream. Schau dir dieses Servlet an. Es wird eine Ausnahme ausgelöst. Die Ausnahme wird in der Datei web.xml einer fehlerhaften JSP zugeordnet:

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();
    }
  }
}

Die Datei web.xml enthält:

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

Und die 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>

Wenn Sie /Erroneousin den Browser laden, wird auf der Fehlerseite "Ein Fehler" angezeigt. Wenn Sie jedoch die out.close()Zeile im obigen Servlet auskommentieren, die Anwendung erneut bereitstellen und neu laden /Erroneous, wird im Browser nichts angezeigt. Ich habe keine Ahnung, was tatsächlich passiert, aber ich denke, das out.close()verhindert die Fehlerbehandlung.

Getestet mit Tomcat 7.0.50, Java EE 6 unter Verwendung von Netbeans 7.4.

user1872904
quelle