Heroku + Spark framework: uploading a file works locally, but not on the server

357 Views Asked by At

I'm using Heroku and Spark framework to upload a .json file.

HTML:

<form method="post" enctype="multipart/form-data" action="UploadJson">
    <input type="file" name="import_file" accept=".json" />
    <button>Upload</button>
</form&gt;

Java:

post("/UploadJson", "multipart/form-data", (request, response) -> {
    String location = "/public/res";
    long maxFileSize = 100000000;
    long maxRequestSize = 100000000;
    int fileSizeThreshold = 1024;
    MultipartConfigElement multipartConfigElement = new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold);
    request.raw().setAttribute("org.eclipse.jetty.multipartConfig", multipartConfigElement);

    try (InputStream inputStream = request.raw().getPart("import_file").getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    )

The code works locally, but I get the following errors when deployed in Heroku:

java.io.IOException: No such file or directory
 at java.io.UnixFileSystem.createFileExclusively(Native Method)
 at java.io.File.createTempFile(File.java:2024)
 at org.eclipse.jetty.util.MultiPartInputStreamParser$MultiPart.createFile(MultiPartInputStreamParser.java:138)
 at org.eclipse.jetty.util.MultiPartInputStreamParser$MultiPart.write(MultiPartInputStreamParser.java:116)
 at org.eclipse.jetty.util.MultiPartInputStreamParser.parse(MultiPartInputStreamParser.java:690)
 at org.eclipse.jetty.util.MultiPartInputStreamParser.getParts(MultiPartInputStreamParser.java:405)
 at org.eclipse.jetty.server.Request.getParts(Request.java:2311)
 at org.eclipse.jetty.server.Request.getParts(Request.java:2290)
 at org.eclipse.jetty.server.Request.getPart(Request.java:2279)
 at javax.servlet.http.HttpServletRequestWrapper.getPart(HttpServletRequestWrapper.java:386)
 at Main.lambda$main$7(Main.java:146)

The exception occurs in the

request.raw().getPart("import_file").getInputStream() part
2

There are 2 best solutions below

2
On

Heroku's file system is immutable, meaning you can't write to it. You'll need to read the file as a stream and send it somewhere else, like S3 or to a database. If you're just trying to read data out of it, you can just eval the json output.

The key is you need to choose the correct stream readers. By default, many of the methods like you have above place things on the file system in a temp directory as a cache while the streams are being accessed. This doesn't work in Heroku since you can't write files anywhere. So you have to use the correct stream reader functions. That will depend on what multipart library you're using.

2
On

I managed to find a workaround based on Nathan Loyer's suggestion. Posting here in case anyone finds it helpful:

$(document).ready(function() {
    $("#import_file").on("change", function(event) {
        var file = event.target.files[0]; 
        var reader = new FileReader();

        reader.onload = function(evt) {
            if (evt.target.readyState != 2) return;
            if (evt.target.error) {
                alert('Error while reading file');
                return;
            }

            $("#json_contents").html(evt.target.result);
        };
        reader.readAsText(file);
    });
});