Micronaut | 3 ways to upload files via HTTP and how to test it.

Photo by Siora Photography on Unsplash

A few days ago i had to upload a zip file to a server. At first i had trouble finding a good method to upload it, but then i decided to go with the Micronaut way, because i’m using this framework anyway. On the website were three different methods to upload the files.

Upload with StreamingFileUpload

The StreamingFileUpload uploads the whole file in small chunks and not at once. Especially with big files, this is a really important thing to have. The Syntax is quite simple. First you have the Post annotation in which we define the location of the link to upload to. It is important to use MULTIPART_FORM_DATA for the consumes section and TEXT_PLAIN for the produces section. The upload function itself creates a tempFile, which you can change to your fileupload location. It transfers the uploaded file to the desired location, in this case the tempFile, and gives a return value true if the upload was successfull. If the value was true, it will return an HttpResponse.ok and if it was false, it will return an HttpResponse Conflict with the message “Upload failed”.

To upload files with this method you can use a simple curl command:

curl -F "file=@YOUR_ZIP_FILE.zip" localhost:8080

That’s pretty easy and straight forward. But what if you don’t want to save the file directly and first perform some actions on it? This can get pretty complicated pretty fast.

Upload a ByteArray

The benefit of uploading a ByteArray instead of using a StreamingFileUpload is, that you don’t have to save the file first, before you take actions on it. For example, if you want to unzip the uploaded files first and then saving the already unzipped files to the filesystem, this comes in pretty handy. You could create a function like this one to handle the whole unzipping process.

The fileName is the location, in which the unzipped file should be saved. This function creates an inputStream with the byteArray and uses it to generate a sequence of zipEntries. It filters out the __MACOSX folder and replaces all backslashes with normal slashes if they exist. Afterwards, it creates the entry in the parentfile of the targetFile. Last, it uses the outputstream to copy the zipStream to the outputStream, which has the same location as the fileName.

This would already be a pretty good solution, but it can be a little bit inconvenient pass the file and also the filename when uploading a file, especially if the fielname and the file are the same input value.

curl -F "file=@YOUR_ZIP_FILE.zip;fileName=YOUR_ZIP_ZILE_NAME.ZIP" localhost:8080

Upload with a CompletedFileUpload

For me, this was the easiest and best solution to my problem. The CompletedFileUpload has the functions getBytes() and getFileName(), which i can directly use in my extract function. With this way, i can prevent the user from putting the same filename in the curl command twice. It would look something like this.

curl -F "file=@YOUR_ZIP_FILE.zip" localhost:8080

Note that this is the exact same command like the StreamingFileUpload. This is because there are nearly the same, with the difference, that this function uploads the whole file at once and the StreamingFileUpload uploads the file in small chunks. Another really important thing to mention is, that the StreamingFileUpload does NOT have the getBytes() method, which is essential for our way of extraction of the ZipFile.

Test the successful Upload

I used AssertJ to test the functionalities of my uploads. I will only show you my integration tests, because other tests are specific for your own code. The first thing we have to look at is the success. To simulate an upload, we have to prepare first a requestBody with everything needed in it. In our case, it needs a name, a filename, the content type and the file itself. After that, we can build it with build(). Afterwards, we have to create a HttpRequest to the location the upload function is listening to and provide the request body. In this case it is just a slash.

For the response, we use the line:

client.toBlocking().exchange<MultipartBody, String>(request, String::class.java)

This essentially just catches the the generated response of the request and saves it to a variable.

The last part of this test are the assertions. In this case we want to test the status, the contentType and the body message.

Test the failed Upload

We also have to test what happens when the upload failes at one point. The requestBody is practically the same with the difference that the file is not a zip anymore. Instead we use a txt format, which should be causing an error when uploading.

Because every HttpResponse that is not a Response.OK throws an Exception, we have to assert that it will throw one. The Exception type in this case is HttpClientResponseException. Since we have asserted that the Exception will be thrown, we can use this exception to get the response status and the contentType. The body is a little different then with the successful upload. The body is always empty in these kinds of exceptions. Because of that, we have to use the message, which is essentially just the content of the body.

I hope this article was useful to you and now your able to use these upload methods by yourself.

Developer Experience Engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store