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.

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.

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
    ...
}