Documentation

You are viewing the documentation for the 2.6.0-M4 development release. The latest stable release series is 3.0.x.

§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();
    }
}

§Testing the file upload

You can also write an automated JUnit test to your upload action:

@Test
public void testFileUpload() throws IOException {
    File file = getFile();
    Http.MultipartFormData.Part<Source<ByteString, ?>> part = new Http.MultipartFormData.FilePart<>("picture", "file.pdf", "application/pdf", FileIO.fromFile(file));

    Http.RequestBuilder request = Helpers.fakeRequest().uri(routes.MyController.upload().url())
            .method("POST")
            .header(Http.HeaderNames.CONTENT_TYPE, "multipart/form-data")
            .bodyMultipart(
                    Collections.singletonList(part),
                    play.libs.Files.singletonTemporaryFileCreator(),
                    app.getWrappedApplication().materializer()
            );

    Result result = Helpers.route(app, request);
    String content = Helpers.contentAsString(result);
    assertThat(content, CoreMatchers.equalTo("File uploaded"));
}

Basically, we are creating a Http.MultipartFormData.FilePart that is required by RequestBuilder method bodyMultipart. Besides that, everything else is just like unit testing controllers.

§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, HttpErrorHandler errorHandler) {
        super(materializer, config.parser().maxDiskBuffer(), errorHandler);
    }

    /**
     * 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