Skip to main content

Handling a multipart request

Armeria provides Multipart encoder and decoder built on top of Reactive Streams.

Building a multipart request

A Multipart consists of multiple BodyParts. Each BodyPart is divided into headers and a body. A BodyPart headers can be created simply using ContentDisposition. String, HttpData, or StreamMessage can be set as the body of the BodyPart.

tip

You can also use HttpHeaders to set complex headers for BobyPart.headers().

import java.nio.file.Path;
import com.linecorp.armeria.common.ContentDisposition;
import com.linecorp.armeria.common.multipart.BodyPart;

// Create a 'Content-Disposition' header with the 'name' parameter set to 'name'.
ContentDisposition nameDisposition =
ContentDisposition.of("form-data", "name");
// Create a BodyPart with 'Content-Disposition' header and its data.
BodyPart bodyPart1 = BodyPart.of(nameDisposition, "Meri Kim");

// Create a 'Content-Disposition' header with the name parameter set to "image",
// and the filename parameter set to "profile.png"
ContentDisposition imageDisposition =
ContentDisposition.of("form-data", "image", "profile.png");
Path image = Paths.get("/path/to/image");
// Create a BodyPart with 'Content-Disposition' header and its file.
BodyPart bodyPart2 = BodyPart.of(imageDisposition, StreamMessage.of(image));

// Create a new multipart with the two body parts.
Multipart multipart = Multipart.of(bodyPart1, bodyPart2);

If we encode and print the Multipart created above,

for (HttpData httpData : multipart.toStreamMessage().collect().join()) {
System.out.print(httpData.toStringUtf8());
}

we can see how the Multipart is encoded as shown below:

--ArmeriaBoundaryEsbNVr9Z66DAIYIN
content-disposition:form-data; name="name"
content-type:text/plain

Meri Kim
--ArmeriaBoundaryEsbNVr9Z66DAIYIN
content-disposition:form-data; name="image"; filename="profile.png"
content-type:application/octet-stream

<binary-data>
--ArmeriaBoundaryEsbNVr9Z66DAIYIN--

Sending a multipart request

The Multipart created in this way can be converted to an HttpRequest through Multipart.toHttpRequest() and transmitted to a server using a WebClient.

WebClient client = WebClient.of("https://armeria.dev");
// Encode a `Multipart` into an `HttpRequest`
HttpRequest request = multipart.toHttpRequest("/upload");
client.execute(request).aggregate()...;

Receiving a multipart request

On the server side, the multipart request sent from the client can be decoded into a Multipart using Multipart.from().

Server.builder()
.service((ctx, req) -> {
// Decode an `HttpRequest` into a `Multipart`
Multipart multipart = Multipart.from(req);
...
})

Since Multipart does not have the actual multipart data, you can use Multipart.bodyParts().subscribe(...) to read data little by little as needed. If the size of the data is not large, the data can be read after being loaded into memory through Multipart.aggregate().

// Use a `Subscriber` to read the data with backpressure.
multipart.bodyParts().subsribe(new Subscriber() {
...
});

// Read the data after aggregation.
Multipart.from(req).aggregate()
.thenAccept(multipart -> {
for (AggregatedBodyPart bodyPart : multipart.bodyParts()) {
String content = bodyPart.contentUtf8();
...
}
});

Using @Param annotation

In annotated services, a body part content of multipart/form-data can be directly mapped into a String, Path, File, or MultipartFile using the @Param annotation.

tip

Note that a BodyPart can be converted into a Path, File or MultipartFile only when the BodyPart.filename() and BodyPart.name() is specified.

import java.io.File;
import java.nio.file.Path;

import com.linecorp.armeria.common.MediaTypeNames;
import com.linecorp.armeria.common.multipart.MultipartFile;
import com.linecorp.armeria.server.annotation.Consumes;
import com.linecorp.armeria.server.annotation.Post;

@Consumes(MediaTypeNames.MULTIPART_FORM_DATA)
@Post("/upload")
public HttpResponse upload(
@Param String param,
@Param File file,
@Param Path path,
@Param MultipartFile multipartFile) {
// Do something with the multipart data
...
}
tip

ServerBuilder.multipartUploadsLocation() and the files are removed as soon as the request is handled completely by default, i.e. when the RequestLog is complete. If you want to persist the uploaded file, you can move the uploaded file to another folder or persistence layer before the deletion.

Alternatively, you can disable the automatic deletion of the uploaded files by setting MultipartRemovalStrategy.NEVER to ServerBuilder.multipartRemovalStrategy(), but please note that you must make sure the uploaded files are deleted once they are not in use to avoid the excessive consumption of disk space.

Like Armeria?
Star us ⭐️

×