Tomcat: getOutputStream() has already been called
Christian Heindl
- java
Hallo allerseits!
ich habe mir ein JSP-File geschrieben welches es mir auf Abruf einen Dateinamen aus der Datenbank holt und diese Datei dann an den Browser sendet/senden soll. Naja, manchmal wird das Bild (testweise habe ich ein Bild zum abrufen, das JSP soll aber später alle möglichen Dateien an den Browser senden) angezeigt, manchmal nicht. Allerdings erhalte ich jedesmal in den Ausgaben von Tomcat eine Exception, wie die untenstehende.
Hier der Code den Tomcat wohl nicht so mag:
<snip>
BufferedInputStream filein = new BufferedInputStream(new FileInputStream(fileName));
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(512);
int objByte;
while((objByte = filein.read()) != -1) {
byteStream.write(objByte);
}
filein.close();
response.reset();
response.setContentType(mimeType);
response.setContentLength(byteStream.size());
byteStream.writeTo(response.getOutputStream()); //<- Hier tritt die Exception auf!
</snip>
Kennt jemand das Problem und hat eine Lösung parat? Ich wäre Euch sehr dankbar!
MfG
Christian Heindl
--- Und hier die vollständige Exception ---
java.lang.IllegalStateException: getOutputStream() has already been called
at org.apache.tomcat.facade.HttpServletResponseFacade.getWriter(Unknown Source)
at org.apache.jasper.runtime.JspWriterImpl.initOut(Unknown Source)
at org.apache.jasper.runtime.JspWriterImpl.flushBuffer(Unknown Source)
at bmm_getobj_10._jspService(bmm_getobj_10.java:185)
at org.apache.jasper.runtime.HttpJspBase.service(Unknown Source)
at javax.servlet.http.HttpServlet.service(HttpServlet.java)
at org.apache.tomcat.facade.ServletHandler.doService(Unknown Source)
at org.apache.tomcat.core.Handler.invoke(Unknown Source)
at org.apache.tomcat.core.Handler.service(Unknown Source)
at org.apache.tomcat.facade.ServletHandler.service(Unknown Source)
at org.apache.tomcat.core.ContextManager.internalService(Unknown Source)
at org.apache.tomcat.core.ContextManager.service(Unknown Source)
at org.apache.tomcat.modules.server.Ajp13Interceptor.processConnection(Unknown Source)
at org.apache.tomcat.util.net.TcpWorkerThread.runIt(Unknown Source)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(Unknown Source)
at java.lang.Thread.run(Thread.java:484)
Hi,
das bedeutet, dass Du versuchst, den OutputStream zu "fetchen", nachdem er - direkt oder indirekt - benutzt wurde.
Wenn Du Dir die entsprechenden Quellcode-Dateien im tomcat/work/DeinContext-Verzeichnis analysierst, siehst Du das auch:
"JspWriter out = null;"
und
"pageContext = _jspxFactory.getPageContext(this, request, *response*, ....);"
"out = pageContext.getOut();" // hierdurch wird über "10 Ecken", u.a. durch Erzeugung eines JSPWriters-Objekts "response.getWriter()" aufgerufen -> somit ist die Bedingung für das Werfen der IllegalStateException erfüllt!
Dies geschieht (zumindest mit Tomcat) also automatisch, wenn Du JSPs verwendest.
Als Workaround fällt mir auf Anhieb ein (ungetestet):
1.
<%@ page contentType="DeinMimeTyp" %>
<% response.sendRedirect("DeineURL/DeinContext/DeinFileName"); %>
Dazu muss sich die Datei im Pfad des entsprechenden Web-Kontextes befinden (wenn Context-Switching erlaubt ist, sollte auch ein anderer funktionieren).
2. Verlagere diese Funktionalität komplett in ein Servlet. Hier kannst Du mit "response.getOutputStream()" problemlos arbeiten.
Viel Grüße,
Martin
<%@ page contentType="DeinMimeTyp" %>
<% response.sendRedirect("DeineURL/DeinContext/DeinFileName"); %>
Dazu muss sich die Datei im Pfad des entsprechenden Web-Kontextes befinden (wenn Context-Switching erlaubt ist, sollte auch ein anderer funktionieren).
Und das will ich eigentlich mit dem JSP verhindern: Ich möchte nicht alle Daten öffentlich zugänglich machen in dem ich das Verzeichniss, in dem die Bilddateien lagern, in den Servlet-Context stelle. In der JSP werden nämlich auch noch Zugriffsrechte (die nur mit der Datenbank zu prüfen sind) geprüft.
Ich habe aber intwischen eine Lösung gefunden:
bevor ich byteStream.writeTo(response.getOutputStream()); (o.s.a.) aufrufe rufe ich jetzt vorher nochmal out.clear(); auf. Damit funktionierts dann auch.
- Verlagere diese Funktionalität komplett in ein Servlet. Hier kannst Du mit "response.getOutputStream()" problemlos arbeiten.
Wäre auch gegangen, allerdings fällt das im Gesamtprojekt etwas aus dem Rahmen, weil nur JSP´s inkl. Beans genutzt werden.
Trotzdem Danke.
Mit freundlichen Grüßen
Christian
Hi,
Und das will ich eigentlich mit dem JSP verhindern: Ich möchte nicht alle Daten öffentlich zugänglich machen in dem ich das Verzeichniss, in dem die Bilddateien lagern, in den Servlet-Context stelle.
Das hat mit der JSP selbst nichts zu tun, sondern mit der Implementierung der Methode "forward()".
In der JSP werden nämlich auch noch Zugriffsrechte (die nur mit der Datenbank zu prüfen sind) geprüft.
Warum implementierst Du das alles in einer JSP?
Die JSP/Servlet-Technologie wurde eingeführt, um die *HTML*-Präsentation von der Content-Generation und allen anderen Backend-Funktionalitäten zu trennen (weswegen die aus den JSP genererierten Servlets auch standardmäßig den Content-Type des HTTP-Responses auf "text/html" setzen).
Ich habe aber intwischen eine Lösung gefunden:
bevor ich byteStream.writeTo(response.getOutputStream()); (o.s.a.) aufrufe rufe ich jetzt vorher nochmal out.clear(); auf. Damit funktionierts dann auch.
Eine technisch funktionierende Lösung zwar, aber keine sehr sinnvolle, denn Du setzt einen für einen anderen Zweck gedachten, bereits initialisierten HTTP-Response komplett zurück, um ihn dann neu zu erzeugen.
Was steht in der JSP eigentlich vor dem zitierten Code-Fragment?
Viele Grüße,
Martin
Das hat mit der JSP selbst nichts zu tun, sondern mit der Implementierung der Methode "forward()".
forward() kenne ich nicht! Wo zu finden?
In der JSP werden nämlich auch noch Zugriffsrechte (die nur mit der Datenbank zu prüfen sind) geprüft.
Warum implementierst Du das alles in einer JSP?
Wieso alles? Es gibt ein Bean rights, das eine Hashtable mit allen Rechten (r,w,ext-w) führt. Im JSP selber wird nur noch geprüft, ob einmal das Bean noch in der Session ist, ob der Benutzer auf die Objekte, die er anfordert (über verschiedenen Paramter im Query-String), r-Zugriff hat, und ob das Objekt überhaupt existiert.
Dann wird aus der Datenbank der Dateiname und der MimeTyp geholt und mit dem geposteten Code-Stück an den Browser gesandt. Schon ist das Ding fertig, und verschlingt nicht mehr als 65 Zeilen. (Da würde ein Servlet evtl sogar mehr Zeilen verschlingen, jedenfalls solange das JSP im Rohzustand bleibt und nicht von Tomcat in ein richtiges Servlet verwandelt wird).
Eine technisch funktionierende Lösung zwar, aber keine sehr sinnvolle, denn Du setzt einen für einen anderen Zweck gedachten, bereits initialisierten HTTP-Response komplett zurück, um ihn dann neu zu erzeugen.
Ok, vielleicht nagt diese doppelte Initialisierung des Output-Streams an der Performance, aber nicht so stark, dass es ein Mensch wirklich merken würde (ohne die Systemzeit zu stoppen).
Aber ich glaube zu verstehen was du eben mit 'JSP = Trennung zwischen Backend-Funktionalitäten und HTML-Präsentation' meinst. In der Hinsicht ist das natürlich Humbug. Mal sehen ob ich später noch die Gelegenheit habe, dass ganze in eine Servlet umzuwandeln (ist ja nicht viel Arbeit, aber die Zeit ist knapp).
Mit freundlichen Grüßen
Christian Heindl