ควรเรียก. close () บน HttpServletResponse.getOutputStream () /. getWriter () หรือไม่


96

ใน Java Servlets เราสามารถเข้าถึงเนื้อหาตอบสนองผ่านresponse.getOutputStream()หรือresponse.getWriter(). ควรเรียกร้อง.close()สิ่งนี้OutputStreamหลังจากที่เขียนถึงหรือไม่?

ในแง่หนึ่งมีการเตือน Blochian ให้ปิดOutputStreams เสมอ ในทางกลับกันฉันไม่คิดว่าในกรณีนี้จะมีทรัพยากรพื้นฐานที่จำเป็นต้องปิด การเปิด / ปิดซ็อกเก็ตได้รับการจัดการที่ระดับ HTTP เพื่ออนุญาตสิ่งต่างๆเช่นการเชื่อมต่อแบบต่อเนื่องเป็นต้น


คุณไม่ได้รับเชิญให้เดาว่ามีทรัพยากรที่จำเป็นต้องปิดอยู่หรือไม่ หากผู้ดำเนินการคิดอย่างนั้นหรือค่อนข้างจะรู้เช่นนั้นเขาจะจัดหาสิ่งclose()ที่ไม่ทำอะไรเลย สิ่งที่คุณควรทำคือปิดทุกทรัพยากรที่สามารถปิดได้
user207421

2
แม้ว่ารหัสของคุณจะไม่เปิดขึ้นมา? ฉันไม่คิดอย่างนั้น ...
Steven Huwig

คำตอบ:


94

โดยปกติคุณไม่ควรปิดสตรีม คอนเทนเนอร์ servlet จะปิดสตรีมโดยอัตโนมัติหลังจากที่ servlet ทำงานเสร็จสิ้นโดยเป็นส่วนหนึ่งของวงจรชีวิตของคำขอ servlet

ตัวอย่างเช่นถ้าคุณปิดกระแสมันจะไม่สามารถใช้ได้ถ้าคุณดำเนินการกรอง

ถ้าคุณปิดมันจะไม่มีอะไรเลวร้ายเกิดขึ้นตราบใดที่คุณไม่พยายามใช้มันอีก

แก้ไข: ลิงค์ตัวกรองอื่น

EDIT2: adrian.tarau ถูกต้องหากคุณต้องการแก้ไขการตอบสนองหลังจากที่ servlet ทำสิ่งนั้นเสร็จแล้วคุณควรสร้าง wrapper ที่ขยาย HttpServletResponseWrapper และบัฟเฟอร์เอาต์พุต นี่คือเพื่อป้องกันไม่ให้เอาต์พุตไปยังไคลเอนต์โดยตรง แต่ยังช่วยให้คุณสามารถป้องกันได้หาก servlet ปิดสตรีมตามข้อความที่ตัดตอนมานี้ (เน้นของฉัน):

ตัวกรองที่ปรับเปลี่ยนการตอบสนองต้อง มักจะจับการตอบสนองก่อนที่มันจะถูกส่งกลับไปยังลูกค้า วิธีดำเนินการนี้คือส่ง servlet ที่สร้างการตอบสนองสตรีมสแตนด์อิน สตรีมสแตนด์อินป้องกันไม่ให้ servlet ปิดสตรีมการตอบกลับเดิมเมื่อดำเนินการเสร็จสิ้นและอนุญาตให้ตัวกรองแก้ไขการตอบสนองของ servlet

บทความ

เราสามารถอนุมานได้จากบทความทางการของ Sun ว่าการปิดOutputStreamจาก servlet เป็นสิ่งที่เกิดขึ้นตามปกติ แต่ไม่ได้บังคับ


2
นี่คือความถูกต้อง สิ่งหนึ่งที่ควรทราบก็คือในบางกรณีคุณอาจต้องล้างสตรีมซึ่งเป็นสิ่งที่อนุญาตอย่างสมบูรณ์
toluju

2
มีผลข้างเคียงของการปิดนักเขียนอีก คุณจะไม่สามารถตั้งรหัสสถานะผ่าน response.setStatus ได้หลังจากปิดไปแล้ว
che javara

1
ทำตามคำแนะนำนี้ จะช่วยให้คุณประหยัดความเจ็บปวดได้มาก ฉันจะไม่ล้าง () เช่นกันเว้นแต่คุณจะรู้ว่าทำไมคุณถึงทำ - คุณควรปล่อยให้คอนเทนเนอร์จัดการบัฟเฟอร์
Hal50000

76

กฎทั่วไปคือหากคุณเปิดสตรีมคุณควรปิด ถ้าคุณไม่ได้คุณไม่ควร ตรวจสอบให้แน่ใจว่ารหัสนั้นสมมาตร

ในกรณีนี้การHttpServletResponseตัดที่ชัดเจนน้อยกว่าเล็กน้อยเนื่องจากไม่ชัดเจนว่าการโทรgetOutputStream()เป็นการดำเนินการที่เปิดสตรีม Javadoc แค่บอกว่ามัน " Returns a ServletOutputStream"; ในทำนองเดียวกันสำหรับgetWriter(). ไม่ว่าจะด้วยวิธีใดสิ่งที่ชัดเจนก็คือHttpServletResponse"เป็นเจ้าของ" สตรีม / ผู้เขียนและสตรีม (หรือคอนเทนเนอร์) มีหน้าที่ปิดอีกครั้ง

ดังนั้นเพื่อตอบคำถามของคุณ - ไม่คุณไม่ควรปิดสตรีมในกรณีนี้ คอนเทนเนอร์ต้องทำเช่นนั้นและหากคุณเข้าไปที่นั่นก่อนหน้านั้นคุณอาจเสี่ยงต่อการนำข้อบกพร่องที่ละเอียดอ่อนมาใช้ในแอปพลิเคชันของคุณ


ฉันเห็นด้วยกับคำตอบนี้คุณอาจต้องการชำระเงิน ServletResponse.flushBuffer () ดู: docs.oracle.com/javaee/1.4/api/javax/servlet/…
cyber-พระ

14
"ถ้าคุณเปิดสตรีมก็ควรปิดถ้าไม่เปิดก็ไม่ควร" --- พูดได้ดี
Srikanth Reddy Lingala

2
ให้ความรู้สึกเหมือนประโยคที่โพสต์บนกระดานของโรงเรียน "ถ้าคุณเปิดก็ปิดถ้าคุณเปิดก็ปิดถ้าคุณปลดล็อกก็ล็อกมันไว้ [... ]"
Ricardo

ฉันสังเกตเห็นว่าฉันใช้สตรีมในช่วงต้นรหัสของฉันและไม่เคยอีกเลยไคลเอนต์รอให้ servlet ทั้งหมดดำเนินการ แต่เมื่อฉันโทรclose()เมื่อฉันเสร็จสิ้นกับสตรีมไคลเอนต์จะกลับมาทันทีและส่วนที่เหลือของ servlet ยังคงดำเนินการต่อ นั่นทำให้คำตอบมีความสัมพันธ์กันมากขึ้นหรือไม่? เมื่อเทียบกับใช่หรือไม่ใช่
ขั้นสุดท้าย

"ถ้าคุณเปิดสตรีมคุณควรปิดหากคุณไม่ได้เปิดคุณไม่ควรตรวจสอบให้แน่ใจว่าโค้ดนั้นสมมาตร" - แล้วถ้าคุณสร้างสตรีมอื่นที่ห่อหุ้มสตรีมนี้ล่ะ? ยากที่จะอยู่แบบสมมาตรในกรณีนี้เนื่องจากการโทรออกจากสตรีมด้านนอกมักจะปิดกระแสที่ซ้อนกัน
steinybot

5

หากมีโอกาสที่กรองอาจจะเรียกว่าบน 'รวมถึง' ทรัพยากรคุณควรแน่นอนไม่ใกล้ลำธาร ซึ่งจะทำให้ทรัพยากรที่รวมไว้ล้มเหลวโดยมีข้อยกเว้น "สตรีมปิด"


ขอบคุณสำหรับการเพิ่มความคิดเห็นนี้ ฉันยังคงต่อสู้กับปัญหาอยู่นิดหน่อย - ตอนนี้ฉันสังเกตเห็นว่าเทมเพลต servlet ของ NetBeans มีโค้ดเพื่อปิดสตรีมเอาต์พุต ...
Steven Huwig

4

คุณควรปิดสตรีมโค้ดจะสะอาดกว่าเนื่องจากคุณเรียกใช้ getOutputStream () และสตรีมจะไม่ถูกส่งผ่านไปยังคุณเป็นพารามิเตอร์โดยปกติแล้วคุณจะใช้เพียงอย่างเดียวและอย่าพยายามปิด Servlet API ไม่ได้ระบุว่าหากสามารถปิดสตรีมเอาต์พุตได้หรือไม่ต้องปิดในกรณีนี้คุณสามารถปิดสตรีมได้อย่างปลอดภัยคอนเทนเนอร์ใด ๆ ที่อยู่ในนั้นจะดูแลการปิดสตรีมหาก servlet ไม่ได้ปิด

นี่คือวิธีการปิด () ในท่าเทียบเรือพวกเขาปิดสตรีมหากไม่ได้ปิด

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

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

        super.close();
    }

นอกจากนี้ในฐานะผู้พัฒนาตัวกรองคุณไม่ควรคิดว่า OutputStream ไม่ได้ถูกปิดคุณควรส่ง OutputStream อื่นเสมอหากคุณต้องการแก้ไขเนื้อหาหลังจากที่ servlet ทำงานเสร็จแล้ว

แก้ไข: ฉันมักจะปิดสตรีมและฉันไม่มีปัญหากับ Tomcat / Jetty ฉันไม่คิดว่าคุณจะมีปัญหากับตู้คอนเทนเนอร์ทั้งเก่าหรือใหม่


4
"คุณควรปิดสตรีมโค้ดจะสะอาดกว่า ... " - สำหรับฉันแล้วโค้ดที่มี .close () ดูสะอาดน้อยกว่าโค้ดที่ไม่มีโดยเฉพาะอย่างยิ่งถ้าไม่จำเป็นต้องใช้. close () ซึ่งเป็นคำถามนี้ พยายามที่จะตรวจสอบ
Steven Huwig

1
ใช่ แต่ความสวยงามตามมา :) อย่างไรก็ตามเนื่องจาก API ไม่ชัดเจนฉันต้องการปิดรหัสจึงดูสอดคล้องกันเมื่อคุณขอ OutputStream คุณควรปิดมันเว้นแต่ API จะบอกว่า "ไม่ปิด"
adrian.tarau

ฉันไม่คิดว่าการปิดสตรีมเอาต์พุตในโค้ดของฉันเองเป็นแนวทางปฏิบัติที่ดี นั่นคืองานของตู้คอนเทนเนอร์
JimHawkins

get * ไม่ได้สร้างสตรีมไม่ใช่ผู้ผลิต คุณไม่ควรปิดมันภาชนะต้องทำ
dmatej

4

อีกข้อโต้แย้งเกี่ยวกับการปิดไฟล์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ในเบราว์เซอร์คุณจะเห็นหน้าข้อผิดพลาดแสดง "ข้อผิดพลาด" แต่ถ้าคุณยกเลิกการแสดงความคิดเห็นout.close()บรรทัดใน servlet ด้านบนให้ปรับใช้ de application อีกครั้งและโหลดซ้ำ/Erroneousคุณจะไม่เห็นอะไรเลยในเบราว์เซอร์ ฉันไม่มีเงื่อนงำเกี่ยวกับสิ่งที่เกิดขึ้นจริง แต่ฉันเดาว่ามันout.close()ป้องกันการจัดการข้อผิดพลาด

ทดสอบกับ Tomcat 7.0.50, Java EE 6 โดยใช้ Netbeans 7.4


0

หากคุณใช้ Spring กับ Spring Security คุณไม่ควรปิดสตรีมหรือผู้เขียน

สตรีมที่ส่งคืนจากServletResponse.getOutputStream()หรือผู้เขียนที่ส่งคืนจากServletResponse.getWriter()จะตอบสนองเมื่อปิด การตอบกลับตามที่อธิบายไว้ที่นี่หมายความว่าสถานะ http และส่วนหัวจะไม่เปลี่ยนรูปและ Spring framework จะไม่สามารถปรับสถานะ 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 200เป็นค่าเริ่มต้นของมัน

หลังจากนั้นข้อยกเว้นจะเริ่มแพร่กระจายจากตัวควบคุมนี้ไปยังตัวจัดการข้อผิดพลาด Spring จัดการข้อผิดพลาดฤดูใบไม้ผลิจะไม่สามารถที่จะเปลี่ยนสถานะเป็นเพราะการตอบสนองก็มุ่งมั่นที่เรียบร้อยแล้วดังนั้นสถานะจะยังคงอยู่500200

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.