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

REST Client Reactiveの使用

このガイドでは、REST Client Reactiveを使用して REST API と対話する方法について説明します。REST Client Reactiveは、RESTEasy Reactive と互換性のある REST クライアント実装です。

アプリケーションがクライアントを使用し、REST エンドポイントを公開する場合は、サーバー部分に RESTEasy Reactive を使用してください。

前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.8.1+

  • 使用したい場合、 Quarkus CLI

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

ソリューション

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

Git リポジトリーのクローンを作成: git clonehttps://github.com/quarkusio/quarkus-quickstarts.git、または アーカイブ をダウンロードします。

ソリューションは rest-client-reactive-quickstart directory にあります。

Maven プロジェクトの作成

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

CLI
quarkus create app org.acme:rest-client-reactive-quickstart \
    --extension=resteasy-reactive-jackson,rest-client-reactive-jackson \
    --no-code
cd rest-client-reactive-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-reactive-quickstart \
    -Dextensions="resteasy-reactive-jackson,rest-client-reactive-jackson" \
    -DnoCode
cd rest-client-reactive-quickstart

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

このコマンドは、REST エンドポイントを持つ Maven プロジェクトを生成し、 rest-clientresteasy-jackson のエクステンションをインポートします。

  • REST サーバーサポート用の resteasy-reactive-jackson エクステンション。ジャクソンを使用したくない場合は、代わりに resteasy-reactive を使用してください。

  • REST クライアントサポート用の rest-client-reactive-jackson エクステンション。Jackson を使用したくない場合は、代わりに rest-client-reactive を使用してください。

すでに Quarkus プロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトに rest-client-reactive-jackson エクステンションを追加することができます。

CLI
quarkus extension add 'rest-client-reactive-jackson'
Maven
./mvnw quarkus:add-extension -Dextensions="rest-client-reactive-jackson"
Gradle
./gradlew addExtension --extensions="rest-client-reactive-jackson"

これにより、 pom.xml に以下が追加されます:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-rest-client-reactive-jackson")

モデルのセットアップ

このガイドでは、 stage.code.quarkus.io サービスが提供する REST API の一部を利用する方法をデモします。まず最初に、使用するモデルを Extension POJO の形式でセットアップします。

src/main/java/org/acme/rest/client/Extension.java ファイルを作成し、以下の内容を設定します。

package org.acme.rest.client;

import java.util.List;

public class Extension {

    public String id;
    public String name;
    public String shortName;
    public List<String> keywords;

}

上記のモデルは、サービスによって提供されるフィールドのサブセットに過ぎませんが、このガイドの目的には十分です。

インターフェースの作成

REST Client Reactive の使用は、適切な JAX-RS および MicroProfile アノテーションを使用してインターフェイスを作成するのと同じくらい簡単です。この場合、インターフェイスは src/main/java/org/acme/rest/client/ExtensionsService.java に作成され、次の内容が含まれている必要があります。

package org.acme.rest.client;

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

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);
}

getById メソッドは、私たちのコードに、Code Quarkus API からidを指定してエクステンションを問い合わせる機能を与えます。クライアントがすべてのネットワーキングとマーシャリングを処理してくれるので、このような技術的な詳細は一切ありません。

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

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

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

quarkus-rest-client-reactive-jackson エクステンションがインストールされている場合、メディアタイプが @Produces または @Consumes アノテーションによって明示的に設定されていない限り、Quarkus はデフォルトでほとんどの戻り値に application/json メディアタイプを使用します。

JSON のデフォルトに頼らない場合は、エンドポイントに @Produces@Consumes のアノテーションを付けて、期待されるコンテンツタイプを正確に定義することを強くお勧めします。これにより、ネイティブ実行可能ファイルに含まれる JAX-RS プロバイダ (コンバータとみなすことができます) の数を絞り込むことができます。

上記の getById メソッドはブロッキング呼び出しです。イベントループで呼び出さないでください。非同期サポート セクションでは、非ブロッキング呼び出しを行う方法について説明します。

Query Parameters

The easiest way to specify a query parameter is to annotate a client method parameter with the @QueryParam or the @RestQuery. The @RestQuery is equivalent of the @QueryParam, but with optional name. Additionally, it can be also used to pass query parameters as a Map, which is convenient if parameters are not known in advance.

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestQuery;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Map;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") Integer id);

    @GET
    Set<Extension> getByName(@RestQuery String name); (1)

    @GET
    Set<Extension> getByFilter(@RestQuery Map<String, String> filter); (2)

    @GET
    Set<Extension> getByFilters(@RestQuery MultivaluedMap<String, String> filters); (3)

}
1 Request query will include parameter with key name
2 Each Map entry represents exactly one query parameter
3 MultivaluedMap allows you to send array values

パスパラメーター

GET リクエストにパスパラメーターが必要な場合は、@QueryParam の代わりに (あるいは加えて) @PathParam ("parameter-name") アノテーションを使用することができます。パスパラメーターとクエリーパラメーターは、必要に応じて以下の例のように組み合わせることができます。

package org.acme.rest.client;

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

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    @Path("/stream/{stream}")
    Set<Extension> getByStream(@PathParam("stream") String stream, @QueryParam("id") String id);
}

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

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

# Your configuration properties
quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=https://stage.code.quarkus.io/api # (1)
1 この設定を行うと、org.acme.rest.client.ExtensionsService を使用して実行されるすべてのリクエストは、ベース URL として https://stage.code.quarkus.io/api を使用することを意味します。上記の設定を使用すると、ExtensionsServicegetById メソッドを io.quarkus:quarkus-rest-client-reactive という値で呼び出すと、https://stage.code.quarkus.io/api/extensions?id=io.quarkus:quarkus-rest-client-reactive に HTTP GET リクエストが行われることになります。

org.acme.rest.client.ExtensionsService は、前のセクションで作成した ExtensionsService インターフェイスの完全修飾名と 一致しなければならない ことに注意してください。

設定を容易にするために、@RegisterRestClient configKey プロパティーを使用して、インターフェイスの完全修飾名とは別の設定ルートを使用することができます。

@RegisterRestClient(configKey="extensions-api")
public interface ExtensionsService {
    [...]
}
# Your configuration properties
quarkus.rest-client.extensions-api.url=https://stage.code.quarkus.io/api
quarkus.rest-client.extensions-api.scope=javax.inject.Singleton

JAX-RS リソースの作成

src/main/java/org/acme/rest/client/ExtensionsResource.java ファイルを以下の内容で作成してください:

package org.acme.rest.client;

import io.smallrye.common.annotation.Blocking;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.Set;

@Path("/extension")
public class ExtensionsResource {

    @RestClient (1)
    ExtensionsService extensionsService;


    @GET
    @Path("/id/{id}")
    @Blocking (2)
    public Set<Extension> id(String id) {
        return extensionsService.getById(id);
    }
}

このリストには 2 つの興味深い部分があります。

1 クライアントスタブには、通常の CDI @Inject ではなく、@RestClient アノテーションが挿入されます。
2 クライアントで行っている呼び出しがブロックされているため、REST エンドポイントに @Blocking アノテーションが必要です

RestClientBuilder を使用したプログラムによるクライアントの作成

クライアントに @RegisterRestClient アノテーションを付け、クライアントに @RestClient を挿入する代わりに、プログラムで REST クライアントを作成することもできます。これは RestClientBuilder で行います。

このアプローチでは、クライアントインターフェイスは次のようになります。

package org.acme.rest.client;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);
}

そして、次のようなサービス:

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.RestClientBuilder;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.net.URI;
import java.util.Set;

@Path("/extension")
public class ExtensionsResource {

    private final ExtensionsService extensionsService;

    public ExtensionsResource() {
        extensionsService = RestClientBuilder.newBuilder()
            .baseUri(URI.create("https://stage.code.quarkus.io/api"))
            .build(ExtensionsService.class);
    }

    @GET
    @Path("/id/{id}")
    public Set<Extension> id(String id) {
        return extensionsService.getById(id);
    }
}

テストの更新

次に、エンドポイントに加えられた変更を反映させるために、機能テストを更新する必要があります。src/test/java/org/acme/rest/client/ExtensionsResourceTest.java ファイルを編集し、テストの内容を以下のように変更します。

package org.acme.rest.client;

import io.quarkus.test.junit.QuarkusTest;

import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.greaterThan;

@QuarkusTest
public class ExtensionsResourceTest {

    @Test
    public void testExtensionsIdEndpoint() {
        given()
            .when().get("/extension/id/io.quarkus:quarkus-rest-client-reactive")
            .then()
            .statusCode(200)
            .body("$.size()", is(1),
                "[0].id", is("io.quarkus:quarkus-rest-client-reactive"),
                "[0].name", is("REST Client Reactive"),
                "[0].keywords.size()", greaterThan(1),
                "[0].keywords", hasItem("rest-client"));
    }
}

The code above uses REST Assured's json-path capabilities.

非同期サポート

クライアントのリアクティブな性質を最大限に活用するには、REST Client Reactive エクステンションのノンブロッキングフレーバーを使用するとよいでしょう。このエクステンションは CompletionStageUni をサポートしています。それでは、実際に ExtensionsService REST インターフェイスに getByIdAsync メソッドを追加して、この動きを見てみましょう。コードは以下のようになります。

package org.acme.rest.client;

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

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;
import java.util.concurrent.CompletionStage;

@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @GET
    CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);
}

src/main/java/org/acme/rest/client/ExtensionsResource.java ファイルを開き、以下の内容で更新してください。

package org.acme.rest.client;

import io.smallrye.common.annotation.Blocking;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.Set;
import java.util.concurrent.CompletionStage;

@Path("/extension")
public class ExtensionsResource {

    @RestClient
    ExtensionsService extensionsService;


    @GET
    @Path("/id/{id}")
    @Blocking
    public Set<Extension> id(String id) {
        return extensionsService.getById(id);
    }

    @GET
    @Path("/id-async/{id}")
    public CompletionStage<Set<Extension>> idAsync(String id) {
        return extensionsService.getByIdAsync(id);
    }
}

呼び出しがノンブロッキングになったので、エンドポイントに @Blocking アノテーションが不要になったことに注意してください。これは、idAsync メソッドがイベントループで呼び出されることを意味します。つまり、ワーカープールのスレッドにオフロードされないので、ハードウェアリソースの使用量を減らすことができます。

非同期メソッドをテストするには、ExtensionsResourceTest に以下のテストメソッドを追加します。

@Test
public void testExtensionIdAsyncEndpoint() {
    given()
        .when().get("/extension/id-async/io.quarkus:quarkus-rest-client-reactive")
        .then()
        .statusCode(200)
        .body("$.size()", is(1),
            "[0].id", is("io.quarkus:quarkus-rest-client-reactive"),
            "[0].name", is("REST Client Reactive"),
            "[0].keywords.size()", greaterThan(1),
            "[0].keywords", hasItem("rest-client"));
}

Uni 版は非常に似ています。

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {

    // ...

    @GET
    Uni<Set<Extension>> getByIdAsUni(@QueryParam("id") String id);
}

ExtensionsResource は次のようになります。

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.Set;

@Path("/extension")
public class ExtensionsResource {

    @RestClient
    ExtensionsService extensionsService;


    // ...

    @GET
    @Path("/id-uni/{id}")
    public Uni<Set<Extension>> idUni(String id) {
        return extensionsService.getByIdAsUni(id);
    }
}
Mutiny

前のスニペットでは、Mutinyのリアクティブ型を使用していますが、もし慣れていない場合は、まず Mutiny - 直感的なリアクティブプログラミングライブラリ を読んでください。

Uni を返すとき、すべての サブスクリプション は 、リモートサービスを呼び出します。つまり、 Uni で再購読してリクエストを再送信するか、以下のように retry を使用することができます。

@RestClient ExtensionsService extensionsService;

// ...

extensionsService.getByIdAsUni(id)
    .onFailure().retry().atMost(10);

CompletionStage を使用する場合は、サービスのメソッドを呼び出して再試行する必要があります。この違いは、Mutinyとそのサブスクリプションプロトコルの lazy 性の側面から来ています。これについての詳細は Mutiny の ドキュメント を参照してください。

カスタムヘッダーのサポート

REST 呼び出しのカスタムヘッダーを指定する方法はいくつかあります。

  • @RegisterClientHeaders アノテーションを用いて ClientHeadersFactory または ReactiveClientHeadersFactory を登録する。

  • @ClientHeaderParam でヘッダーの値を指定する

  • @HeaderParam でヘッダーの値を指定する

以下のコードは、これらの各手法の使用方法を示しています。

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders(RequestUUIDHeaderFactory.class) (1)
@ClientHeaderParam(name = "my-header", value = "constant-header-value") (2)
@ClientHeaderParam(name = "computed-header", value = "{org.acme.rest.client.Util.computeHeader}") (3)
public interface ExtensionsService {

    @GET
    @ClientHeaderParam(name = "header-from-properties", value = "${header.value}") (4)
    Set<Extension> getById(@QueryParam("id") String id, @HeaderParam("jaxrs-style-header") String headerValue); (5)
}
1 クラスごとに存在できる ClientHeadersFactory は 1 つだけです。これを使用すると、カスタムヘッダーを追加できるだけでなく、既存のヘッダーを変換することもできます。ファクトリーの例については、以下の RequestUUIDHeaderFactory クラスを参照してください。
2 @ClientHeaderParam は、クライアントインターフェイスとメソッドで使用できます。一定のヘッダー値を指定できます。
3 そして、ヘッダーの値を計算するメソッドの名前を指定します。このインターフェイスでは、静的メソッドまたはデフォルトメソッドのいずれかです。
4 また、アプリケーションの設定値も使用します。
5 あるいは、通常の JAX-RS の @HeaderParam アノテーションされた引数として使用します。

Kotlin を使用するときにデフォルトのメソッドを利用する場合は、Java のデフォルトのインターフェイス機能を使用するように Kotlin コンパイラーを設定する必要があります。詳細は、こちら を参照してください。

ClientHeadersFactory は次のようになります。

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.util.UUID;

@ApplicationScoped
public class RequestUUIDHeaderFactory implements ClientHeadersFactory {

    @Override
    public MultivaluedMap<String, String> update(MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> clientOutgoingHeaders) {
        MultivaluedMap<String, String> result = new MultivaluedHashMap<>();
        result.add("X-request-uuid", UUID.randomUUID().toString());
        return result;
    }
}

上の例のように、 @Singleton, @ApplicationScoped などのスコープを定義するアノテーションを付けることで、 ClientHeadersFactory の実装をCDI Beanにすることができます。

${header.value} に値を指定するには、application.properties に以下のように記述するだけです。

header.value=value of the header

また、ブロッキング操作を実行できる ClientHeadersFactory のリアクティブフレーバーがあります。例えば:

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;

import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.util.UUID;

@ApplicationScoped
public class GetTokenReactiveClientHeadersFactory extends ReactiveClientHeadersFactory {

    @Inject
    Service service;

    @Override
    public Uni<MultivaluedMap<String, String>> getHeaders(
            MultivaluedMap<String, String> incomingHeaders,
            MultivaluedMap<String, String> clientOutgoingHeaders);
        return Uni.createFrom().item(() -> {
            MultivaluedHashMap<String, String> newHeaders = new MultivaluedHashMap<>();
            // perform blocking call
            newHeaders.add(HEADER_NAME, service.getToken());
            return newHeaders;
        });
    }
}

デフォルトのヘッダーファクトリー

@RegisterClientHeaders アノテーションは、カスタムファクトリーを指定せずに使用することもできます。その場合、DefaultClientHeadersFactoryImpl ファクトリーが使用されます。REST リソースから REST クライアント呼び出しを行う場合、このファクトリーは、org.eclipse.microprofile.rest.client.propagateHeaders 設定プロパティーにリストされているすべてのヘッダーをリソースリクエストからクライアントリクエストに伝播させます。個々のヘッダー名はコンマで区切られます。

@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @GET
    CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);
}
org.eclipse.microprofile.rest.client.propagateHeaders=Authorization,Proxy-Authorization

例外処理

MicroProfile REST クライアント仕様では、HTTP 応答を例外に変換することを目的とした org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper が導入されています。

上記の ExtensionsService にこのような ResponseExceptionMapper を実装する簡単な例は、次のとおりです。

パブリッククラス MyResponseExceptionMapper は ResponseExceptionMapper を実装します <RuntimeException> {

    @Override
    public RuntimeException toThrowable(Response response) {
        if (response.getStatus() == 500) {
            throw new RuntimeException("The remote service responded with HTTP 500");
        }
        return null;
    }
}

ResponseExceptionMapper は、ResponseExceptionMapper 実装が呼び出される優先度を決定するために使用される getPriority メソッドも定義します (getPriority の値が小さい実装が最初に呼び出されます)。toThrowable が例外を返す場合、その例外が出力されます。null が返された場合、チェーン内の ResponseExceptionMapper の次の実装が呼び出されます (存在する場合)。

上記のクラスは、REST クライアントによって自動的に使用されることはありません。アプリケーションのすべての REST クライアントで使用できるようにするには、クラスに @Provider アノテーションを付ける必要があります (quarkus.rest-client-reactive.provider-autodiscoveryfalse に設定されていない場合)。または、例外処理クラスを特定の REST クライアントインターフェイスにのみ適用する必要がある場合は、インターフェイスに @RegisterProvider(MyResponseExceptionMapper.class) でアノテーションを付けるか、適切な quarkus.rest-client 設定グループの providers プロパティー設定を使用して登録できます。

@ClientExceptionMapper を使用する

400 以上の HTTP 応答コードを変換する簡単な方法は、@ClientExceptionMapper アノテーションを使用することです。

上記で定義された ExtensionsService REST クライアントインターフェイスの場合、@ClientExceptionMapper の使用例は次のようになります。

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @GET
    CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);

    @ClientExceptionMapper
    static RuntimeException toException(Response response) {
        if (response.getStatus() == 500) {
            return new RuntimeException("The remote service responded with HTTP 500");
        }
        return null;
    }
}

当然、この処理は REST クライアントごとに行われます。@ClientExceptionMapper は、priority 属性が設定されておらず、すべてのハンドラーを呼び出す通常のルールが適用される場合、デフォルトの優先度を使用します。

マルチパートフォームのサポート

REST Client Reactive はマルチパートメッセージをサポートします。

マルチパートメッセージの送信

REST Client Reactive を使用すると、データをマルチパート形式で送信できます。このようにして、たとえばファイルを効率的に送信できます。

マルチパートフォームとしてデータを送信するには、送信するすべてのフィールドをカプセル化するクラスを作成する必要があります。例えば、次のようにします。

public class FormDto {
    @FormParam("file")
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    public File file;

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

フォームを送信するメソッドは、消費されたメディアタイプとしてマルチパートフォームデータを指定する必要があります。例えば、次のようにします。

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/binary")
    String sendMultipart(@MultipartForm FormDto data);

File`、Pathbyte[] または Buffer として指定されたフィールドはファイルとして送信されます。@PartType(MediaType.APPLICATION_OCTET_STREAM) の場合はバイナリーファイル、その他のコンテンツタイプの場合はテキストファイルとして送信されます。その他のフィールドはフォームの属性として送信されます。

フォームデータのエンコードには、いくつかのモードがあります。デフォルトでは、Rest Client Reactive は RFC1738 を使用します。クライアントレベルで、io.quarkus.rest.client.multipart-post-encoder-mode RestBuilder プロパティーを HttpPostRequestEncoder.EncoderMode の選択した値に設定するか、application.propertiesquarkus.rest-client.multipart-post-encoder-mode を指定して、モードを上書きすることができます。後者は @RegisterRestClient アノテーションで作成されたクライアントに対してのみ機能することに注意してください。利用可能なすべてのモードは、Netty documentation に記載されています。

マルチパートメッセージの受信

REST Client Reactive は、マルチパートメッセージの受信もサポートしています。送信と同様に、マルチパートレスポンスを解析するには、レスポンスデータを記述するクラスを作成する必要があります。

public class FormDto {
    @RestForm (1)
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    public File file;

    @FormParam("otherField") (2)
    @PartType(MediaType.TEXT_PLAIN)
    public String textProperty;
}
1 省略形の @RestForm アノテーションを使用して、マルチパートフォームの一部としてフィールドを作成します
2 標準の @FormParam も使用できます。マルチパートパートの名前を上書きできます。

次に、呼び出しに対応するインターフェイスメソッドを作成し、FormDto を返すようにします。

    @GET
    @Produces(MediaType.MULTIPART_FORM_DATA)
    @Path("/get-file")
    FormDto data sendMultipart();

現時点では、マルチパート応答のサポートには次の制限があります。

  • マルチパート応答で送信されたファイルは、FilePath、および FileDownload にのみ解析できます。

  • 応答タイプの各フィールドには @PartType アノテーションを付ける必要があります - このアノテーションのないフィールドは無視されます

REST Client Reactive は、マルチパートリターンタイプとして使用されるクラスを事前に知っている必要があります。 multipart/form-data を生成するインターフェイスメソッドがある場合、戻り型は自動的に検出されます。ただし、ClientBuilder API を使用して応答をマルチパートとして解析する場合は、DTO クラスに @MultipartForm のアノテーションを付ける必要があります。

ダウンロードしたファイルは自動的に削除されず、多くのディスク領域を占有する可能性があります。作業が終わったら、ファイルを削除することを検討してください。

プロキシーサポート

REST Client Reactive は、プロキシーを介したリクエストの送信をサポートしています。JVM 設定を尊重しますが、次の両方を指定することもできます。

  • グローバルクライアントプロキシー設定、 quarkus.rest-client.proxy-addressquarkus.rest-client.proxy-userquarkus.rest-client.proxy-passwordquarkus.rest-client.non-proxy-hosts

  • quarkus.rest-client.<my-client>.proxy-address などのクライアントごとのプロキシー設定です。これらは、CDI で挿入されたクライアント、つまり @RegisterRestClient で作成されたクライアントに対してのみ適用されます。

proxy-address がクライアントレベルで設定されている場合、クライアントは特定のプロキシー設定を使用します。プロキシー設定は、グローバル設定または JVM プロパティーから伝播されません。

クライアントに proxy-address が設定されていないが、グローバルレベルで設定されている場合、クライアントはグローバル設定を使用します。それ以外の場合、クライアントは JVM 設定を使用します。

プロキシーを設定するための設定例:

# global proxy configuration is used for all clients
quarkus.rest-client.proxy-address=localhost:8182
quarkus.rest-client.proxy-user=<proxy user name>
quarkus.rest-client.proxy-password=<proxy password>
quarkus.rest-client.non-proxy-hosts=example.com

# per-client configuration overrides the global settings for a specific client
quarkus.rest-client.my-client.proxy-address=localhost:8183
quarkus.rest-client.my-client.proxy-user=<proxy user name>
quarkus.rest-client.my-client.proxy-password=<proxy password>
quarkus.rest-client.my-client.url=...
MicroProfile REST クライアント仕様では、プロキシー資格情報の設定は許可されていません。プログラムでプロキシーユーザーとプロキシーパスワードを指定するには、RestClientBuilderRestClientBuilderImpl にキャストする必要があります。

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

アプリケーションを実行します。

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

このエクステンションに関する基本的な情報を含む JSON オブジェクトが表示されるはずです。

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

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

そして次のコマンドで実行できます。java -jar target/quarkus-app/quarkus-run.jar

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

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

トラフィックの記録

REST Client Reactive は、送信するリクエストと受信する応答をログに記録できます。ロギングを有効にするには、quarkus.rest-client.logging.scope プロパティーを quarkus.rest-client.logging.scope に追加し、次のように設定します。

  • リクエストと応答の内容をログに記録する request-response 、または

  • all は、基礎となるライブラリーの低レベルのロギングも有効にします。

HTTP メッセージには大きな本文が含まれる可能性があるため、ログに記録される本文の文字数を制限します。デフォルトの制限は 100 ですが、quarkus.rest-client.logging.body-limit を指定することで変更できます。

REST Client Reactive は、レベル DEBUG でトラフィックをログに記録しており、ロガーのプロパティーを変更しません。この機能を使用するには、ロガー設定を調整する必要がある場合があります。

ロギング設定の例:

quarkus.rest-client.logging.scope=request-response
quarkus.rest-client.logging.body-limit=50

quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG

テストのためのクライアントのモッキング

@RestClient アノテーションが挿入されたクライアントを使用する場合は、テスト用に簡単にモックすることができます。Mockito の @InjectMock または QuarkusMock でそれを行うことができます。

このセクションでは、クライアントをモックに置き換える方法を示します。Quarkus でモッキングがどのように機能するかをより深く理解したい場合は、 Mocking CDI beans のブログ投稿を参照してください。

@NativeImageTest または @QuarkusIntegrationTest を使用すると、モッキングは機能しません。

次のクライアントがあると仮定します。

package io.quarkus.it.rest.client.main;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

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


@Path("/")
@RegisterRestClient
public interface Client {
    @GET
    String get();
}

InjectMock でモックする

テスト用にクライアントをモックする最も簡単な方法は、Mockito と @InjectMock を使用することです。

まず、以下の依存関係をアプリケーションに追加します。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5-mockito</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-junit5-mockito")

次に、テストで @InjectMock を使用して、モックを作成して挿入できます。

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;

@QuarkusTest
public class InjectMockTest {

    @InjectMock
    Client mock;

    @BeforeEach
    public void setUp() {
        when(mock.get()).thenReturn("MockAnswer");
    }

    @Test
    void doTest() {
        // ...
    }
}

QuarkusMock でモックする

Mockito がニーズを満たさない場合は、QuarkusMock を使用してプログラムでモックを作成できます。例:

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusMock;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class QuarkusMockTest {

    @BeforeEach
    public void setUp() {
        Client customMock = new Client() { (1)
            @Override
            public String get() {
                return "MockAnswer";
            }
        };
        QuarkusMock.installMockForType(customMock, Client.class, RestClient.LITERAL); (2)
    }
    @Test
    void doTest() {
        // ...
    }
}
1 ここでは、手動で作成したクライアントインターフェイスの実装を使用して、実際のクライアントを置き換えます
2 RestClient.LITERALinstallMockForType メソッドの最後の引数として渡される必要があることに注意してください

テストにモックHTTPサーバーを使用する

場合によっては、クライアント自体をモックするのではなく、リモートエンドポイント (HTTP サーバー) をモックしたい場合があります。これは、ネイティブテスト、またはプログラムで作成されたクライアントに特に役立つ場合があります。

Wiremock を使用して HTTP サーバーを簡単にモックできます。Wiremock section of the Quarkus - Using the REST Client では、セットアップ方法について詳しく説明しています。

既知の制限

REST Client Reactive エクステンションは、REST Client エクステンションをそのまま置き換えることを目的としていますが、いくつかの相違点と制限があります。

  • 新しいエクステンションのクライアントのデフォルトスコープは @ApplicationScoped ですが、quarkus-rest-client のデフォルトは @Dependent です。この動作を変更するには、quarkus.rest-client-reactive.scope プロパティーを完全修飾形式のスコープ名に設定します。

  • HostnameVerifier または SSLContext を設定することができない

  • ExecutorService の設定など、非ブロッキング実装には意味をなさないいくつかのことが機能しません