Grails
  1. Grails
  2. GRAILS-6049

Render template to string and write direct to response

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Critical 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:

      ExportController.groovy
      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

        Issue Links

          Activity

          Hide
          Dillon Bly added a comment -

          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."

          Show
          Dillon Bly added a comment - 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."
          Hide
          Dillon Bly added a comment -

          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:

          ResponseFreeMailService.groovy
          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
              }
          
          Show
          Dillon Bly added a comment - 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: ResponseFreeMailService.groovy 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 }
          Hide
          przemyslaw added a comment -

          I see this problem in Grails 1.3.1 and Grails 1.3.2 too.

          Show
          przemyslaw added a comment - I see this problem in Grails 1.3.1 and Grails 1.3.2 too.
          Hide
          Peter McNeil added a comment - - edited

          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.

          Show
          Peter McNeil added a comment - - edited 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.
          Show
          Graeme Rocher added a comment - Fixed: http://github.com/grails/grails-core/commit/d42db48440fe7579a95863f856623a0da1ffec7f

            People

            • Assignee:
              Graeme Rocher
              Reporter:
              przemyslaw
            • Votes:
              14 Vote for this issue
              Watchers:
              15 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development