Blog

Uploads and downloads, inputs and outputs

By  
Matti Tahvonen
Matti Tahvonen
·
On Jan 4, 2019 8:20:00 AM
·
In Product

Vaadin Upload asks the developer to provide an OutputStream where the framework can stream the file content. Vaadin StreamResource (~ download) asks the developer to provide an InputStream the framework can read and stream to a user. Intuitive? Not really, unless you have accustomed to this inversion during your Vaadin career.

For some reason, over a decade ago, when designing these APIs, we took the opposite strategy than what the Servlet specification did. Vaadin kind of turns the setup upside down from what the underlying Servlet specification provides. For certain trivial cases, like streaming a static file to end user, or streaming end user’s file to a static file in the server, the Vaadin approach makes sense. But, for most dynamic use cases, you'll want to read the contents of the uploaded file and write the custom content you wish your user to download. Just like in Servlet specification, you get InputStream from the request and OutputStream from the response. The differences, in the case of file upload, are visualized in the figure below.

vaadn-vs-servlet-upload

This might be a place for a major overhaul at some point, but we have survived ages with this approach and we will do so in the future, if necessary. But how to tackle this in your Vaadin code? There are workarounds and solutions, official and unofficial.

Buffering

This is the most common way to solve the problem, but not necessarily the most efficient one. When receiving an upload from your client, you can provide a ByteArrayOutputStream for Vaadin or stream the content to a file. Then you can hook in an UploadFinishedListener to actually handle the content from your buffer. Similarly, you can provide a ByteArrayOutputStream for your dynamic content creation (e.g. PDF generation) and then finally wrap the byte array with ByteArrayInputStream that you can provide for Vaadin. Or do the same with a temp file. Vaadin actually nowadays (since Vaadin 10) provides some helpers for this approach. Developers don't need to implement the Receiver interface anymore, but can use one of the ready-made solutions that buffer the file contents for later usage.

There is FileBuffer that streams to temp file and MemoryBuffer that streams to ByteArrayOutputStream. For multi-file uploads, there is MultiFileBuffer and MultiFileMemoryBuffer. BTW, if you just want to store the content of the file in your app (let me guess, database), there is a handy tool called UploadField in the EasyUploads add-on that you can use with Binder (or FieldGroup with Vaadin 7). It just buffers the content, and the value of the field is a byte array containing the file contents.

For Vaadin 10+, the Flow Viritin add-on contains ByteArrayUploadField with similar functionality. But these solutions often use obsolete resources. A more efficient method is to use streaming.

Streaming

In the streaming solution, we turn the setup upside down, again, and this way we get more optimal API for dynamic purposes. We pipe one stream to another and provide the piped version forward.

File downloads

Let's think about dynamic file generation first. The name of the API Vaadin provides for this use case is StreamResource. The API has changed radically in Vaadin 10 and it now provides a built-in feature for piping/streaming. It has two constructors and if you use the one that takes in StreamResourceWriter, you can actually get an API that is pretty similar to what you are used to when writing content using ServletResponse. The provided API is quite limited though (e.g. no way to configure headers), so in non-trivial cases it may be better to use lower level features like RequestHandler (that can be either Session specific or application specific) or fall back all the way to the underlying Servlet API. Viritin project has a helper called DownloadButton to do this inversion for older Vaadin versions (< 10).

DynamicFileDownloader is the tool in Vaadin 10+ compatible Viritin that makes things more agile with the latest versions too.

File uploads

For file uploads the framework doesn't provide a solution yet, but it can sure be implemented by yourself, or you can use third-party add-ons to simplify your life. Flow Viritin has a new UploadFileHandler component that does exactly this. It uses PipedInputStream and PipedOutputStream and provides an API where you can just read the file contents from InputStream and get the name and mime type in the same API call. In the example code, we just simply count the line breaks in the file and report that to the end user. As you see in the example, the streaming portions of your code are not executed in the UI thread, so UI changes must be wrapped into UI.access code block. The same applies when generating files. The synchronization of these methods is missing on purpose, a file upload or possibly a long-running file generation would stall the whole UI.


 public class UploadFileHandlerExample extends VerticalLayout {

    public UploadFileHandlerExample() {
        UploadFileHandler uploadFileHandler = new UploadFileHandler(
                (InputStream content, String fileName, String mimeType)-> {
            try {
                int b = 0;
                int count = 0;
                while ((b = content.read()) != -1) {
                    if (b == "\n".getBytes()[0]) {
                        count++;
                    }
                }
                String msg = "Counted " + count + "lines";
                getUI().get().access(()-> Notification.show(msg));
            } catch (IOException ex) {
                Logger.getLogger(UploadFileHandlerExample.class.getName())
                    .log(Level.SEVERE, null, ex);
            }
        });
        
        add(uploadFileHandler);

    }
}


 

I'm not aware of any helper classes for this kind of streaming file handling for older Framework versions, but I have done it a couple of times to Vaadin projects with a similar approach. So just copy the idea from UploadFileHandler, that is tested to work.

Any other ideas? What do you think about these solutions? How have you dealt with these cases? Do you think we should refactor the API in the next major? Share your ideas in the comments section!

Matti Tahvonen
Matti Tahvonen
Matti Tahvonen has a long history in Vaadin R&D: developing the core framework from the dark ages of pure JS client side to the GWT era and creating number of official and unofficial Vaadin add-ons. His current responsibility is to keep you up to date with latest and greatest Vaadin related technologies. You can follow him on Twitter – @MattiTahvonen
Other posts by Matti Tahvonen