Documentation

§Handling file upload

§Uploading files in a form using multipart/form-data

The standard way to upload files in a web application is to use a form with a special multipart/form-data encoding, which allows mixing of standard form data with file attachments. Please note: the HTTP method for the form has to be POST (not GET).

Start by writing an HTML form:

@form(action = routes.Application.upload, 'enctype -> "multipart/form-data") {

    <input type="file" name="picture">

    <p>
        <input type="submit">
    </p>

}

Now let’s define the upload action:

public Result upload() {
    MultipartFormData<File> body = request().body().asMultipartFormData();
    FilePart<File> picture = body.getFile("picture");
    if (picture != null) {
        String fileName = picture.getFilename();
        String contentType = picture.getContentType();
        File file = picture.getFile();
        return ok("File uploaded");
    } else {
        flash("error", "Missing file");
        return badRequest();
    }
}

§Direct file upload

Another way to send files to the server is to use Ajax to upload files asynchronously from a form. In this case, the request body will not be encoded as multipart/form-data, but will just contain the plain file contents.

public Result upload() {
    File file = request().body().asRaw().asFile();
    return ok("File uploaded");
}

§Writing a custom multipart file part body parser

The multipart upload specified by MultipartFormData takes uploaded data from the request and puts into a TemporaryFile object. It is possible to override this behavior so that Multipart.FileInfo information is streamed to another class, using the DelegatingMultipartFormDataBodyParser class:

public static class MultipartFormDataWithFileBodyParser extends BodyParser.DelegatingMultipartFormDataBodyParser<File> {

    @Inject
    public MultipartFormDataWithFileBodyParser(Materializer materializer, play.api.http.HttpConfiguration config) {
        super(materializer, config.parser().maxDiskBuffer());
    }

    /**
     * Creates a file part handler that uses a custom accumulator.
     */
    @Override
    public Function<Multipart.FileInfo, Accumulator<ByteString, FilePart<File>>> createFilePartHandler() {
        return (Multipart.FileInfo fileInfo) -> {
            final String filename = fileInfo.fileName();
            final String partname = fileInfo.partName();
            final String contentType = fileInfo.contentType().getOrElse(null);
            final File file = generateTempFile();

            final Sink<ByteString, CompletionStage<IOResult>> sink = FileIO.toFile(file);
            return Accumulator.fromSink(
                    sink.mapMaterializedValue(completionStage ->
                            completionStage.thenApplyAsync(results ->
                                    new Http.MultipartFormData.FilePart(partname,
                                            filename,
                                            contentType,
                                            file))
                    ));
        };
    }

    /**
     * Generates a temp file directly without going through TemporaryFile.
     */
    private File generateTempFile() {
        try {
            final EnumSet<PosixFilePermission> attrs = EnumSet.of(OWNER_READ, OWNER_WRITE);
            final FileAttribute<?> attr = PosixFilePermissions.asFileAttribute(attrs);
            final Path path = Files.createTempFile("multipartBody", "tempFile", attr);
            return path.toFile();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

}

Here, akka.stream.javadsl.FileIO class is used to create a sink that sends the ByteString from the Accumulator into a java.io.File object, rather than a TemporaryFile object.

Using a custom file part handler also means that behavior can be injected, so a running count of uploaded bytes can be sent elsewhere in the system.

Next: Accessing an SQL database


Found an error in this documentation? The source code for this page can be found here. After reading the documentation guidelines, please feel free to contribute a pull request.