HttpServletResponse.getOutputStream () /. GetWriter () üzerinde .close () çağrılmalı mı?


96

Java Servletlerinde, yanıt gövdesine response.getOutputStream()veya aracılığıyla erişilebilir response.getWriter(). Yazıldıktan sonra bunun .close()üzerine bir çağrı yapılmalı mı OutputStream?

Bir yandan, her zaman kapatılmaya yönelik Blochian tavsiyesi var OutputStream. Öte yandan, bu durumda kapatılması gereken temel bir kaynak olduğunu düşünmüyorum. Soketlerin açılması / kapanması, kalıcı bağlantılar ve benzeri şeylere izin vermek için HTTP seviyesinde yönetilir.


Kapatılması gereken temel bir kaynak olup olmadığını tahmin etmeye davet edilmezsiniz. Eğer implementor öyle düşünüyor, daha doğrusu bu yüzden bilir, o bir sağlayacaktır close()hiçbir şey yapmaz o. Ne sen yapmalıyım yakın her kapatılabilen bir kaynaktır.
user207421

2
Kodunuz açmasa bile? Sanmıyorum ...
Steven Huwig

Yanıtlar:


94

Normalde akışı kapatmamalısınız. Sunucu uygulaması kapsayıcısı, sunucu uygulaması isteği yaşam döngüsünün bir parçası olarak çalışması bittikten sonra akışı otomatik olarak kapatır.

Örneğin, akışı kapattıysanız, bir Filtre uygularsanız bu kullanılamaz .

Tüm bunları söyledikten sonra, kapatırsanız, tekrar kullanmayı denemediğiniz sürece kötü bir şey olmayacak.

DÜZENLE: başka bir filtre bağlantısı

DÜZENLEME2: adrian.tarau, sunucu uygulaması işini yaptıktan sonra yanıtı değiştirmek istiyorsanız, HttpServletResponseWrapper'ı genişleten bir sarmalayıcı oluşturmanız ve çıktıyı arabelleğe almanız gerektiği konusunda doğrudur. Bu, çıktının doğrudan istemciye gitmesini engellemek içindir, ancak aynı zamanda bu alıntıya göre sunucu uygulaması akışı kapatırsa koruma sağlamanıza da olanak tanır (vurgu benim):

Bir yanıtı değiştiren bir filtre, genellikle istemciye döndürülmeden önce yanıtı yakalamalıdır. Bunu yapmanın yolu, yanıtı oluşturan sunucu uygulamasına bir bekleme akışı geçirmektir. Bekleme akışı, sunucu uygulamasının tamamlandığında orijinal yanıt akışını kapatmasını önler ve filtrenin sunucu uygulamasının yanıtını değiştirmesine izin verir.

makale

Sun'ın resmi makalesinden OutputStreamsunucu uygulamasından kapatmanın normal bir olay olduğu, ancak zorunlu olmadığı sonucuna varılabilir .


2
Doğru. Dikkat edilmesi gereken bir nokta, bazı durumlarda akıntıyı temizlemeniz gerekebileceği ve buna tamamen izin verilebileceğidir.
toluju

2
Yazarı kapatmanın başka bir yan etkisi daha var. Ayrıca, kapatıldıktan sonra da response.setStatus aracılığıyla durum kodunu ayarlayamayacaksınız.
che javara

1
Bu tavsiyeye uyun. Seni çok fazla acıdan kurtaracak. Bunu neden yaptığınızı bilmiyorsanız, ben de flush () yapmam - kabın arabelleğe almayı işlemesine izin vermelisiniz.
Hal50000

76

Bunların genel kuralı şudur: eğer akışı açtıysanız, kapatmalısınız. Eğer yapmadıysan, yapmamalısın. Kodun simetrik olduğundan emin olun.

Bu durumda, HttpServletResponsearamanın getOutputStream()akışı açan bir işlem olup olmadığı açık olmadığından, bu biraz daha az nettir . Javadoc sadece " Returns a ServletOutputStream" diyor ; benzer şekilde için getWriter(). Her iki durumda da, açık olan HttpServletResponse, akışa / yazara "sahip olan" ve onu (veya kapsayıcıyı) tekrar kapatmaktan sorumludur.

Yani sorunuzu cevaplamak için - hayır, bu durumda akışı kapatmamalısınız. Kapsayıcı bunu yapmalıdır ve oraya ondan önce girerseniz, uygulamanızda ince hatalar oluşturma riskiyle karşılaşırsınız.


Bu yanıta katılıyorum, ayrıca ServletResponse.flushBuffer'a () göz atmak
cyber-monk

14
"Eğer akışı açtıysan, kapatmalısın.
Açmadıysan

2
Okul panosuna yazılan cümleler gibi geliyor "eğer açarsan kapat. Açarsan kapat. Kilidini açarsan kilitle. [...]"
Ricardo

Iv, akışı kodumun başlarında kullandığımı ve bir daha asla kullanmadığımı fark etti, istemci tüm sunucu uygulamasının yürütülmesini bekler, ancak close()akışla işim bittiğinde, istemci hemen geri döner ve sunucu uygulamasının geri kalanı çalışmaya devam eder. Bu, cevabı biraz daha göreceli yapmaz mı? kesin bir evet veya hayır yerine
BiGGZ

"Akışı açtıysanız, kapatmalısınız. Açmadıysanız, açmamalısınız. Kodun simetrik olduğundan emin olun." - Peki ya bu akışı saran başka bir akış yaratırsanız? Dış akışı kapatma çağrısı tipik olarak iç içe olanı kapatacağından, bu durumda simetrik kalmak zordur.
steinybot

5

Filtre bir 'dahil' kaynakta adlandırılabilecek herhangi bir şans yapmanız gerekir kesinlikle yoksa değil yakın akışı. Bu, dahil edilen kaynağın bir 'akış kapalı' istisnasıyla başarısız olmasına neden olur.


Bu yorumu eklediğiniz için teşekkürler. Komik bir şekilde, hala sorunla biraz boğuşuyorum - şimdi fark ettim ki NetBeans'in sunucu uygulaması şablonunun çıkış akışını kapatmak için kod içerdiğini fark ettim ...
Steven Huwig

4

Akışı kapatmanız gerekir, kod daha temizdir çünkü getOutputStream () işlevini çağırırsınız ve akış bir parametre olarak size iletilmez, genellikle sadece kullanırsanız ve kapatmaya çalışmazsanız. Servlet API, çıkış akışı kapatılabiliyorsa veya kapatılmaması gerekiyorsa, bu durumda akışı güvenli bir şekilde kapatabileceğinizi, oradaki herhangi bir konteynerin sunucu uygulaması tarafından kapatılmamışsa akışı kapatmakla ilgileneceğini belirtmez.

İşte Jetty'deki close () yöntemi, kapatılmamışsa akışı kapatırlar.

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

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

        super.close();
    }

Ayrıca bir Süzgeç geliştiricisi olarak, OutputStream'in kapalı olmadığını varsaymamalısınız, sunucu uygulaması işini yaptıktan sonra içeriği değiştirmek istiyorsanız her zaman başka bir OutputStream geçirmelisiniz.

DÜZENLEME: Her zaman akışı kapatıyorum ve Tomcat / Jetty ile herhangi bir sorun yaşamadım. Eski ya da yeni herhangi bir kapla ilgili herhangi bir sorun yaşamamanız gerektiğini düşünüyorum.


4
"Akışı kapatmalısınız, kod daha temiz ..." - Bana göre, .close () ile biberli kod, içermeyen koddan daha az temiz görünüyor, özellikle de .close () gereksizse - bu soru da budur belirlemeye çalışıyor.
Steven Huwig

1
evet, ama güzellik sonra gelir :) Her neyse, API net olmadığından kapatmayı tercih ederim, kod tutarlı görünüyor, bir kez bir OutputStream talep ettiğinizde API "kapatmayın" demediği sürece kapatmalısınız.
adrian.tarau

Çıkış akışını kendi kodumda kapatmanın iyi bir uygulama olduğunu düşünmüyorum. Konteynerin işi bu.
JimHawkins

get * akışı yaratmaz, yapımcı değildir. Kapatmamalısın, konteyner yapmalı.
dmatej

4

Kapanış başka görüşe göre OutputStream. Şu servlet'e bak. Bir istisna yaratır. İstisna, web.xml'de bir JSP hatasıyla eşleştirilmiştir:

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 dosyası şunları içerir:

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

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

/ErroneousTarayıcıya yüklediğinizde "Bir hata" görüntüleyen hata sayfasını görürsünüz. Ancak out.close(), yukarıdaki sunucu uygulamasındaki satırın açıklamasını kaldırırsanız, uygulamayı yeniden konuşlandırır ve yeniden yüklerseniz /Erroneous, tarayıcıda hiçbir şey görmezsiniz. Gerçekte ne olduğu hakkında hiçbir fikrim yok, ancak sanırım bu out.close()hata işlemeyi engelliyor.

Tomcat 7.0.50, Java EE 6 ile Netbeans 7.4 kullanılarak test edilmiştir.


0

Bahar Security ile Bahar kullanarak ediyorsanız, olmamalı yakın akışı veya yazar.

Gönderen akış ServletResponse.getOutputStream()veya geri dönen yazar ServletResponse.getWriter()kapanışta yanıtı işleyecektir. Yanıtı burada açıklandığı gibi işlemek, http durumunun ve başlıkların değişmez hale geldiği ve Spring çerçevesinin, bu istek sunulurken istisna atılsa bile http durumunu ayarlayamayacağı anlamına gelir.

Bir OnCommittedResponseWrappersınıf örneği, bu davranışın uygulanması olarak kullanılır ServletResponseve işte bu davranıştan sorumlu olan kod ( javadoc'u da kontrol edin ).

Aşağıdaki örnek denetleyiciyi düşünün:

@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");
        }
    }

İstisna atıldığında, gerçekleşen ilk şey, writer.close()yanıt http durumunu varsayılan değerine donduracak bir çağrıdır 200.

Ancak bundan sonra istisna, bu denetleyiciden Spring hata işleyicilerine yayılmaya başlayacaktır. Yay hata işleyicileri, durumu olarak değiştiremez, 500çünkü yanıt zaten işlenmiştir ve bu nedenle durum kalacaktır 200.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.