Grails create downloadable file on the fly

Mikey picture Mikey · Jun 8, 2011 · Viewed 8.2k times · Source

Briefly: I can make a file, save it in the file system and then make a page with a link to that file, but what if I want a page with links to many files which may not all need to be generated?

So my user clicks a link on the list page like:

<g:link action="gimmeAFile" id="${myThingieInstance.id}">${fieldValue(bean: myThingieInstance, field: "id")}</g:link>

Right now I have a controller that looks like this:

def gimmeAFile = {
  def lotsaLines = []
  //Do a ton of stuff that has lotsaLines.add(resultStrings) all over

  def fileName = "blahblah-${dateOrSomething}.csv"
  def dumbFile = new File('web-app/tmpfiles/'+fileName).withWriter {out ->
    lotsaLines.each{
      out.println it
    }
  }
  [fileName:fileName]
}

And then they go to gimmeAFile.gsp which has the link to actually download the file:

<a href="${resource(dir:'tmpfiles',file:fileName)}">Download Report</a>

How do I make a link on the list viewer that will create and download the file without dragging the user to an extra screen. NOTE: I cannot have the files pre-generated, so I need to figure out how to link to a file that isnt there yet. I'm thinking something like render() at the end of the controller. Can I make the gimmeAFile controller just give the file instead of making a page with a link to the file?

OK so to clarify this is what I finally figured out based on Kaleb's answer. Thankyou SO!!

def gimmeAFile = {
  def lotsaLines = []
  //Do a ton of stuff that has lotsaLines.add(resultStrings) all over

  def fileName = "blahblah-${dateOrSomething}.csv"
  def dumbFile = new File('web-app/tmpfiles/'+fileName).withWriter {out ->
    lotsaLines.each{
      out.println it
    }
  }
  def openAgain = new File('web-app/tmpfiles/'+fileName)
  response.setContentType("text/csv")
  response.setHeader("Content-disposition", "filename=${fileName}")
  response.outputStream << openAgain.getBytes()
  response.outputStream.flush()
  return
}

Answer

Kaleb Brasee picture Kaleb Brasee · Jun 8, 2011

You can create a view that just gets the bytes of the file and writes out to the response:

response.contentType  = 'image/jpeg' // or whatever content type your resources are
response.outputStream << file.getBytes()
response.outputStream.flush()

Is that what you're trying to do?