Details
-
Type:
Bug
-
Status:
Closed
-
Priority:
Critical
-
Resolution: Fixed
-
Affects Version/s: 1.2.1
-
Fix Version/s: 1.3.4
-
Component/s: Controllers
-
Labels:None
-
Environment:Mac OS X 10.6.2, Windows 7, sun java 1.5 and 1.6
Description
Run the ExportController in the attached ExportBug.zip. Grails throws following exception:
java.lang.IllegalStateException: getOutputStream() has already been called for this response
at org.apache.catalina.connector.Response.getWriter(Response.java:610)
at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:198)
at org.codehaus.groovy.grails.web.sitemesh.GrailsNoDecorator.render(GrailsNoDecorator.java:42)
at com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator.render(BaseWebAppDecorator.java:34)
at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.doFilter(GrailsPageFilter.java:149)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
....
Relevant Source Code:
import java.util.zip.ZipOutputStream import java.util.zip.ZipEntry import org.apache.commons.io.IOUtils class ExportController { def index = { def httpout = response.outputStream response.contentType = "application/octet-stream" response.setHeader("Pragma", ""); response.setHeader("Cache-Control", "must-revalidate"); response.setHeader("Content-disposition", "attachment; filename=mytest.zip") ZipOutputStream zipStream = new ZipOutputStream(httpout) zipStream.setLevel(9) def s = g.render(template:"test") zipStream.putNextEntry(new ZipEntry("mytest")); IOUtils.copy(new ByteArrayInputStream(s.getBytes("UTF-8")),zipStream) zipStream.closeEntry() zipStream.flush() httpout.flush() zipStream.close() httpout.close() } }
It seems, using g.render(tempalte) and direct writing to the response.outputstream in the same action is actually not possible without throwing that exception.
I ve tried other contentTypes (e.g. application/octet-stream) and different returns (e.g. return null and return false) but grails always throws that exception.
Probably this bug is related to #GRAILS-1223
-
Hide
- ExportBug.zip
- 18/Mar/10 9:06 AM
- 323 kB
- przemyslaw
-
- ExportBug/.classpath 0.7 kB
- ExportBug/.project 0.5 kB
- ExportBug/.../org.codehaus.groovy.eclipse.preferences.prefs 0.1 kB
- ExportBug/application.properties 0.2 kB
- ExportBug/ExcelExportBug.iml 3 kB
- ExportBug/ExcelExportBug.ipr 13 kB
- ExportBug/ExcelExportBug.iws 31 kB
- ExportBug/grails-app/.../BootStrap.groovy 0.1 kB
- ExportBug/grails-app/.../BuildConfig.groovy 1 kB
- ExportBug/grails-app/conf/Config.groovy 3 kB
- ExportBug/grails-app/.../DataSource.groovy 0.6 kB
- ExportBug/grails-app/.../resources.groovy 0.0 kB
- ExportBug/grails-app/.../UrlMappings.groovy 0.2 kB
- ExportBug/.../ExportController.groovy 0.8 kB
- ExportBug/grails-app/.../messages.properties 3 kB
- ExportBug/.../messages_da.properties 3 kB
- ExportBug/.../messages_de.properties 4 kB
- ExportBug/.../messages_es.properties 3 kB
- ExportBug/.../messages_fr.properties 2 kB
- ExportBug/.../messages_it.properties 2 kB
- ExportBug/.../messages_ja.properties 2 kB
- ExportBug/.../messages_nl.properties 3 kB
- ExportBug/.../messages_pt_BR.properties 3 kB
- ExportBug/.../messages_pt_PT.properties 3 kB
- ExportBug/.../messages_ru.properties 4 kB
- ExportBug/.../messages_th.properties 5 kB
- ExportBug/.../messages_zh_CN.properties 2 kB
- ExportBug/grails-app/views/error.gsp 2 kB
- ExportBug/grails-app/.../export/_test.gsp 0.3 kB
- ExportBug/grails-app/views/index.gsp 3 kB
Issue Links
- is related to
-
GRAILS-7163
Prevent committing original response while Sitemesh is active (before layout is applied)
-
Activity
- All
- Comments
- Work Log
- History
- Activity
- Git Commits
I have had the same problem seen above. From testing, when the problems comes when the buildPdf method calls writeTo on the GroovyPageWritable object. From within a controller, calling the following methods lead to the problem:
def template = groovyPagesTemplateEngine.createTemplate(resource) // using a resource from a view, for example
def writable = template.make(model)
w.writeTo(writer)
Specifically, the 'writeTo' in the GrovyPageWritable object requires the current response object for reasons I don't know... as it should 'writeTo' the writable you give it.
I'm working on a workaround... I'll update when I have it.
g.render will call GrailsWebRequest.getOut under the layers which contains this code:
/** * @return the out */ public Writer getOut() { Writer out = attributes.getOut(getCurrentRequest()); if(out ==null) try { return getCurrentResponse().getWriter(); } catch (IOException e) { throw new ControllerExecutionException("Error retrieving response writer: " + e.getMessage(), e); } return out; }
That causes a call to getWriter() if webRequest.out hasn't been set. Try setting webRequest.out to some java.io.Writer (StringWriter , new StreamCharBuffer().getWriter() etc.) before the g.render line.
Notes on GRAILSPLUGINS-1548 issue might be helpful.
The GRAILSPLUGINS-1548 doesn't help me much. That pinpointed a problem in grails 1.1.x whereas mine worked fine for 1.1 and fails with 1.2
I did try your suggestion for setting the webRequest side, but it did not solve the problem. As the first comment in this bug report points out, the goal is to write binary data to the output stream. After the controller action executes, sitemesh is trying to call 'getOutputStream()' even though the response has already be generated. This behavior (seems to be) different in Grails 1.2 when using the groovyPagesTemplateEngine to render a string as opposed to Grails 1.1.
I've pulled apart the grails code for this rendering and have yet to see what tells the grails server 'do not render' a page when a controller is calling "response.outputStream << object" itself. (Again, remember that calling that method by itself works, its when you use the groovyPagesTemplateEngine without having it write to the response.outputStream that it fails)
One more comment... I also tried setting the ContentType as you mentioned in your GRAILSPLUGINS-1548 comment without success.
You could find out what calls org.apache.catalina.connector.Response.getOutputStream() by setting a breakpoint to that method when debugging by running a grails app with "grails-debug run-app". By default the debugging socket is listening on port 5005 in Grails by default.
Another way to trace calls is to use btrace. I've used this btrace "script" to trace response.setContentType and response.getWriter calls.
You should be able to modify it to trace response.getOutputStream() calls.
import com.sun.btrace.annotations.*; import com.sun.btrace.AnyType; import com.sun.btrace.aggregation.*; import static com.sun.btrace.BTraceUtils.*; @BTrace public class ResponseTracer { @OnMethod( clazz="+javax.servlet.ServletResponse", method="setContentType", type="void (java.lang.String)" ) public static void onSetContentType(AnyType[] arg) { print(strcat("entered ", name(probeClass()))); println(strcat(".", probeMethod())); println(strcat("content-type: ", str(arg[0]))); //jstack(); println(); } @OnMethod( clazz="+javax.servlet.ServletResponse", method="getWriter", type="java.io.PrintWriter() ()" ) public static void onGetWriter(AnyType[] arg) { print(strcat("entered ", name(probeClass()))); println(strcat(".", probeMethod())); if(compareTo("org.apache.catalina.connector.Response", name(probeClass()))==0) { jstack(); } println(); } }
usage example (for running the script I've attached):
lari@mylinuxbox:~$ jps |grep GrailsStarter
28495 GrailsStarter
lari@mylinuxbox:~$ btrace 28495 ResponseTracer.java
(assuming btrace is on the path. btw., you don't have to compile ResponseTracer.java)
Download btrace from http://kenai.com/projects/btrace/downloads/directory/releases . It's an awesome tool.
Thanks, I'll check it out... that will be useful. Though from the exception I see, I know sitemesh is calling it... just not why in one case and not the other.
java.lang.RuntimeException: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: getOutputStream() has already been called for this response at com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator.render(BaseWebAppDecorator.java:40)
I'll check out btrace to see if I can follow this bouncing ball...
Sorry... I didn't post the exception from the 'right' stack trace that grails sends to the output. The original exception is here:
java.lang.IllegalStateException: getOutputStream() has already been called for this response at org.apache.catalina.connector.Response.getWriter(Response.java:610) at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:198) at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:122) at org.codehaus.groovy.grails.web.pages.GSPResponseWriter$1.getWriter(GSPResponseWriter.java:83) at org.codehaus.groovy.grails.web.util.StreamCharBuffer$ConnectedWriter.getWriter(StreamCharBuffer.java:1562) at org.codehaus.groovy.grails.web.util.StreamCharBuffer$SingleOutputWriter.write(StreamCharBuffer.java:1597) at org.codehaus.groovy.grails.web.util.StreamCharBuffer$CharBufferChunk.writeTo(StreamCharBuffer.java:1212) at org.codehaus.groovy.grails.web.util.StreamCharBuffer.writeToImpl(StreamCharBuffer.java:480) at org.codehaus.groovy.grails.web.util.StreamCharBuffer.writeTo(StreamCharBuffer.java:473) at org.codehaus.groovy.grails.web.util.StreamCharBuffer.flushToConnected(StreamCharBuffer.java:699) at org.codehaus.groovy.grails.web.util.StreamCharBuffer.access$900(StreamCharBuffer.java:221) at org.codehaus.groovy.grails.web.util.StreamCharBuffer$StreamCharBufferWriter.flush(StreamCharBuffer.java:905) at org.codehaus.groovy.grails.web.util.GrailsPrintWriter.flush(GrailsPrintWriter.java:103) at org.codehaus.groovy.grails.web.pages.GSPResponseWriter.close(GSPResponseWriter.java:141) at org.codehaus.groovy.grails.web.pages.GroovyPagesServlet.renderPageWithEngine(GroovyPagesServlet.java:146) at org.codehaus.groovy.grails.web.pages.GroovyPagesServlet.doService(GroovyPagesServlet.java:120) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:563) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:646) at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:551) at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:488) at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter$1$1.render(GrailsPageFilter.java:219) at com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator.render(BaseWebAppDecorator.java:34) at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.doFilter(GrailsPageFilter.java:149) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFilterInternal(GrailsReloadServletFilter.java ...
What does your web-app/WEB-INF/sitemesh.xml file look like?
In Grails 1.2 it should contain these settings:
<sitemesh>
<page-parsers>
<parser content-type="text/html"
class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
<parser content-type="text/html;charset=ISO-8859-1"
class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
<parser content-type="text/html;charset=UTF-8"
class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
</page-parsers>
<decorator-mappers>
<mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
</decorator-mappers>
</sitemesh>
("grails upgrade" updates this file)
Have you tried setting content-type before calling render? (Try moving it to the first line in the controller action closure.)
And one more thing: Add "return null" as the last line in the controller action closure.
My sitemesh.xml looks exactly like yours.
I tried following variation too, without success:
class ExportController {
def index = {
response.contentType = "application/octet-stream"
response.setHeader("Pragma", "");
response.setHeader("Cache-Control", "must-revalidate");
response.setHeader("Content-disposition", "attachment; filename=mytest.zip")
def httpout = response.outputStream
ZipOutputStream zipStream = new ZipOutputStream(httpout)
zipStream.setLevel(9)
def s = g.render(template:"test")
zipStream.putNextEntry(new ZipEntry("mytest"));
IOUtils.copy(new ByteArrayInputStream(s.getBytes("UTF-8")),zipStream)
zipStream.closeEntry()
zipStream.flush()
httpout.flush()
zipStream.close()
httpout.close()
return null
}
}
(I am still on Grails 1.2.1)
At this point, I think this is a real bug in grails... unless a grails developer can explain why its not.
Now I do not know if this jira ticket is listing the same bug showing two different ways to get it, or two different bugs.
This has nothing to do with rendering a view. The trouble appears to be that the render tag writes to the GrailsContentBufferingResponse even though it's output is collected as a string and then written directly to the HTTP response. GrailsPageFilter then checks whether there is any content in the GrailsContentBufferingResponse and if so, it applies a decorator. I can't understand why it's applying a decorator in this case, because no layout is specified in a meta tag and there is no layout applicable via convention.
Anyway, I agree this is a bug. The render tag should not be affecting either the HTTP response or the Sitemesh filter when used in this way.
This is a big issue for me because a common pattern in my project is to send an HTML email during a controller action, then render a response to the user, which worked fine in Grails 1.1.1.
def myAction = {
def htmlContents = g.render(template:"/some/template", model:[])
render("Thank you.")
}
Will result in the template being rendered to the HTTP Response instead of the expected text, "Thank you."
In case anyone is having the same problem as me, where rendering an HTML email with the Mail Plugin leaks into the current HTTP Response, here's a service that works around the issue:
import org.springframework.web.context.request.RequestContextHolder /** * This class is a hack to workaround GRAILS-6049 in Grails 1.2.2. */ class ResponseFreeMailService { def mailService /** * Like mailService.sendMail, but reset request attributes to work around * GRAILS-6049, which causes the the rendered email html template to render * to the active HTML response. This hack takes advantage of the fact that * MailMessageBuilder will create a mock request to render the view if * the request attributes are null. */ def sendResponseFreeMail = { Closure callable -> def originalRequestAttributes = RequestContextHolder.getRequestAttributes() try { RequestContextHolder.setRequestAttributes(null) mailService.sendMail(callable) } finally { RequestContextHolder.setRequestAttributes(originalRequestAttributes) } } }
Then replace calls to sendMail with:
def responseFreeMailService
responseFreeMailService.sendMail {
to "joe@test.com"
body(view:"/your/_template", model:[])
// ...etc
}
I see this problem rendering js and css in 1.3.2 too.
Just confirming I'm not using the render tag but I'm doing this:-
def parseFile(file, params) {
def sw = new StringWriter()
try {
Template t = groovyPagesTemplateEngine.createTemplate(file)
Writable w = t.make(model: [params: params])
w.writeTo(sw)
return sw.getBuffer()
} finally {
sw.close()
}
}
I can confirm that if I replace this method with new StringBuffer("waasup doc") it doesn't throw the exception.
Also, if you don't close the response.outputStream it tries to render the conventional view.
I also see this with 1.2.2:
def previewDesign = { SimpleDesignCommand sdc -> def content = g.render(template:"PdfTemplate", model:[title:sdc.title, subTitle:sdc.subTitle, content:sdc.content]).toString() response.setContentType("application/pdf") response.setHeader("Content-disposition", "attachment; filename=design.pdf") response.outputStream << buildPdf(content) }Gives:
Funny thing is that the action does create and return its PDF OK, so perhaps this error message is spurious?