The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

マルチパートでのRESTクライアントの使用

This guide is about the multipart support of the REST Client compatible with RESTEasy Classic which used to be the default JAX-RS implementation until Quarkus 2.8.

It is now recommended to use RESTEasy Reactive, which supports equally well traditional blocking workloads and reactive workloads. For more information about RESTEasy Reactive, please see the REST Client Reactive guide and, for the server side, the introductory REST JSON guide or the more detailed RESTEasy Reactive guide.

RESTEasy has rich support for the multipart/* and multipart/form-data mime types. The multipart mime format is used to pass lists of content bodies. Multiple content bodies are embedded in one message. multipart/form-data is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it.

This guide explains how to use the RESTEasy REST Client with Multipart in order to interact with REST APIs requiring multipart/form-data content-type with very little effort.

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.1+

  • 使用したい場合、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

ソリューション

次のセクションで紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。

Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.gitアーカイブ をダウンロードします。

The solution is located in the rest-client-multipart-quickstart directory.

Mavenプロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。

CLI
quarkus create app org.acme:rest-client-multipart-quickstart \
    --extension=rest-client,resteasy,resteasy-multipart \
    --no-code
cd rest-client-multipart-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-client-multipart-quickstart \
    -Dextensions="rest-client,resteasy,resteasy-multipart" \
    -DnoCode
cd rest-client-multipart-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

This command generates the Maven project with a REST endpoint and imports the rest-client and resteasy extensions. It also adds the resteasy-multipart extension to support multipart/form-data requests.

モデルのセットアップ

In this guide we will be demonstrating how to invoke a REST service accepting multipart/form-data input. We are assuming the payload is well-known before the request is sent, so we can model as a POJO.

If the payload is unknown, you can also use the RESTEasy custom API instead. If that’s the case, see the RESTEasy Multipart Providers link at the end of the guide.

Our first order of business is to set up the model we will be using to define the multipart/form-data payload, in the form of a MultipartBody POJO.

Create a src/main/java/org/acme/rest/client/multipart/MultipartBody.java file and set the following content:

package org.acme.rest.client.multipart;

import java.io.InputStream;

import javax.ws.rs.FormParam;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.providers.multipart.PartType;

public class MultipartBody {

    @FormParam("file")
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    public InputStream file;

    @FormParam("fileName")
    @PartType(MediaType.TEXT_PLAIN)
    public String fileName;
}

上のコードのアノテーションの目的は以下の通りです。

  • @FormParam is a standard JAX-RS annotation used to define a form parameter contained within a request entity body

  • @PartType is a RESTEasy specific annotation required when a client performs a multipart request and defines the content type for the part.

インターフェースの作成

Using the RESTEasy REST Client is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations. In our case the interface should be created at src/main/java/org/acme/rest/client/multipart/MultipartService.java and have the following content:

package org.acme.rest.client.multipart;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;

@Path("/echo")
@RegisterRestClient
public interface MultipartService {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    String sendMultipartData(@MultipartForm MultipartBody data);

}

The sendMultipartData method gives our code the ability to POST a multipart/form-data request to our Echo service (running in the same server for demo purposes). Because in this demo we have the exact knowledge of the multipart/form-data packets, we can map them to the model class created in the previous section using the @org.jboss.resteasy.annotations.providers.multipart.MultipartForm annotation.

The client will handle all the networking and marshalling leaving our code clean of such technical details.

上のコードのアノテーションの目的は以下の通りです。

  • @RegisterRestClient により、Quarkusは、このインターフェイスがRESTクライアントとしてCDIインジェクションに利用可能であることを知ることができます。

  • @Path and @POST are the standard JAX-RS annotations used to define how to access the service

  • @MultipartForm defines the parameter as a value object for incoming/outgoing request/responses of the multipart/form-data mime type.

  • @Consumes defines the expected content-type consumed by this request (parameters)

  • @Produces defines the expected content-type produced by this request (return type)

While @Consumes and @Produces are optional as auto-negotiation is supported, it is heavily recommended to annotate your endpoints with them to define precisely the expected content-types.

It will allow to narrow down the number of JAX-RS providers (which can be seen as converters) included in the native executable.

コンフィグレーションの作成

REST 呼び出しが行われるベース URL を決定するために、REST クライアントは application.properties からの設定を使用します。プロパティーの名前は、以下のコードで表示される特定の規則に従う必要があります。

# Your configuration properties
quarkus.rest-client."org.acme.rest.client.multipart.MultipartService".url=http://localhost:8080/

Having this configuration means that all requests performed using org.acme.rest.client.multipart.MultipartService will use `http://localhost:8080/ ` as the base URL.

Note that org.acme.rest.client.multipart.MultipartService must match the fully qualified name of the MultipartService interface we created in the previous section.

Create the JAX-RS resource

Create the src/main/java/org/acme/rest/client/multipart/MultipartClientResource.java file with the following content:

package org.acme.rest.client.multipart;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;

import javax.inject.Inject;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RestClient;

@Path("/client")
public class MultipartClientResource {

    @Inject
    @RestClient
    MultipartService service;

    @POST
    @Path("/multipart")
    @Produces(MediaType.TEXT_PLAIN)
    public String sendFile() throws Exception {
        MultipartBody body = new MultipartBody();
        body.fileName = "greeting.txt";
        body.file = new ByteArrayInputStream("HELLO WORLD".getBytes(StandardCharsets.UTF_8));
        return service.sendMultipartData(body);
    }
}

Note that in addition to the standard CDI @Inject annotation, we also need to use the MicroProfile @RestClient annotation to inject MultipartService.

Creating the server

For demo purposes, let’s create a simple Echo endpoint that will act as the server part.

Create the directory src/main/java/org/acme/rest/client/multipart/server and include a EchoService.java file with the following content:

package org.acme.rest.client.multipart.server;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/echo")
public class EchoService {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    public String echo(String requestBody) throws Exception {
        return requestBody;
    }
}

This will just return the request body and it’s useful for testing purposes.

テストの更新

We also need to update the functional test to reflect the changes made to the endpoint. Edit the src/test/java/org/acme/rest/client/multipart/MultipartClientResourceTest.java file to:

package org.acme.rest.client.multipart;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
public class MultipartClientResourceTest {

    @Test
    public void testMultipartDataIsSent() {
        given()
                .when().post("/client/multipart")
                .then()
                .statusCode(200)
                .body( containsString("Content-Disposition: form-data; name=\"file\""),
                        containsString("HELLO WORLD"),
                        containsString("Content-Disposition: form-data; name=\"fileName\""),
                        containsString("greeting.txt"));
    }

}

The code above uses REST Assured to assert that the returned content from the echo service contains multipart elements

Because the test runs in a different port, we also need to include an application.properties in our src/test/resources with the following content:

# Your configuration properties
quarkus.rest-client."org.acme.rest.client.multipart.MultipartService".url=http://localhost:8081/

アプリケーションをパッケージ化して実行する

Run the application with:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

In a terminal, run curl -X POST http://localhost:8080/client/multipart

You should see an output similar to:

--89d288bd-960f-460c-b266-64c5b4d170fa
Content-Disposition: form-data; name="fileName"
Content-Type: text/plain

greeting.txt
--89d288bd-960f-460c-b266-64c5b4d170fa
Content-Disposition: form-data; name="file"
Content-Type: application/octet-stream

HELLO WORLD
--89d288bd-960f-460c-b266-64c5b4d170fa--

いつものように、アプリケーションは以下の方法でパッケージ化されます。

CLI
quarkus build
Maven
./mvnw clean package
Gradle
./gradlew build

And executed with java -jar target/quarkus-app/quarkus-run.jar.

次のようにネイティブ実行可能ファイルを生成することもできます。

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native