Another day, another post about play!…
When you extend from play.mvc.Controller, you have access to various render methods. One of these is renderBinary(), which allows you to send binary data (captchas, images, etc) to the browser.
renderBinary() is overloaded, and one of the signatures takes an java.io.InputStream as the source of the binary data. This makes it easy to load, for example, a file from the server into the InputStream and deliver it to the browser. However, what about files that don’t exist anywhere?
More than a few places allow you to tailor what you’re downloading so you only get exactly what you need – JQuery UI springs to mind here. However, when I tried to download JQuery UI for a new project a few weeks ago, I received an error message – /tmp/foo.zip could not be written to the file system, or something similar.
Because renderBinary() renders – surprise – binary, what you’re doing is passing a bytestream into the response. Java provides a java.io.ByteArrayInputStream, which can hold all your bytes (within reasonable memory constraints) and so there is the possibility of creating in-memory files and rendering them.
In this example, I’ll take some files from the server, zip them up in memory and dump them into the response.
public class Foo extends Controller { public static void getZipFile() { ZipOutputStream zos = null; ByteArrayOutputStream bytes = new ByteArrayOutputStream(); FileInputStream fileInput = null; try { zipOutput = new ZipOutputStream(new BufferedOutputStream(bytes)); String fileNames = { "/tmp/foo.txt", "/tmp/bar.txt" }; for (String fileName : fileNames) { ZipEntry entry = new ZipEntry(fileName); zipOutput.putNextEntry(entry); fileInput = new FileInputStream(fileName); byte[] buf = new byte[1024]; int len; while ((len = fileInput.read(buf)) > 0) { zipOutput.write(buf, 0, len); } zos.closeEntry(); // IOUtils.close() just closes the stream if it's not null, and swallows // any exceptions - this is just an example! IOUtils.close(fileInput); } // close the output stream, but crucially this has no effect on the content // of bytes (check the ByteArrayOutputStream javadoc for why) zipOutput.close(); // at this point, bytes contains the zip "file" InputStream zipInput = new ByteArrayInputStream(bytes.toByteArray()); // give the resulting binary a name in the response renderBinary(zipInput, "foobar.zip"); } catch (IOException e) { // whatever } finally { IOUtils.close(fileInput); IOUtils.close(zipOutput); } } }
There are, of course, pros and cons with any approach…
- Pros
- You don’t write to the file system, so you don’t need to clean up the file system
- Should be faster, due to not writing to the file system
- Cons
- More memory intensive – potentially much more memory intensive, depending on your user requirements and what you’re actually assembling in memory
- As written, there’s nothing on the file system for you to check for correctness (easily corrected, in a dev environment)