Using the 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.6

  • 使用したい場合は、 Quarkus CLI

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

ソリューション

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

git clone https://github.com/quarkusio/quarkus-quickstarts.git で Git リポジトリーをクローンします。または、https://github.com/quarkusio/quarkus-quickstarts/archive/2.16.zip[アーカイブ] をダウンロードします。

ソリューションは rest-client-reactive-quickstart ディレクトリ にあります。

Mavenプロジェクトの作成

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

コマンドラインインタフェース
quarkus create app org.acme:rest-client-reactive-quickstart \
    --stream=2.16 \
    --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.16.12.Final:create \
    -DplatformVersion=2.16 \
    -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-client と resteasy-jackson のエクステンションをインポートします。

  • REST サーバーサポート用の resteasy-reactive-jackson エクステンション。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
<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 、 @QueryParam はサービスへのアクセス方法を定義するために使用される標準的な JAX-RS アノテーションです。

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

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

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

クエリーパラメーター

クエリーパラメーターを指定する最も簡単な方法は、クライアントメソッドのパラメーターに @QueryParam または @RestQuery というアノテーションを付けることです。 @RestQuery は @QueryParam と同等ですが、名前を省略することができます。さらに、クエリーパラメーターを Map として 渡すこともでき、事前にパラメーターがわからない場合に便利です。

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 リクエストクエリーには name をキーとするパラメーターが含まれます
2 各 Map エントリは、ちょうど 1 つのクエリーパラメーターを表します
3 MultivaluedMap は配列の値を送信することができます

@ClientQueryParam を使用

リクエストにクエリーパラメーターを追加するもうひとつの方法は、REST クライアントインターフェイスあるいはそのインターフェイスの特定のメソッドに @io.quarkus.rest.client.reactive.ClientQueryParam を使用することです。 アノテーションではクエリーパラメーター名を指定し、値は定数や設定プロパティー、あるいはメソッドの起動時に決定できます。

次の例では、さまざまな使い方を紹介します:

@ClientQueryParam(name = "my-param", value = "${my.property-value}") (1)
public interface Client {
    @GET
    String getWithParam();

    @GET
    @ClientQueryParam(name = "some-other-param", value = "other") (2)
    String getWithOtherParam();

    @GET
    @ClientQueryParam(name = "param-from-method", value = "{with-param}") (3)
    String getFromMethod();

    default String withParam(String name) {
        if ("param-from-method".equals(name)) {
            return "test";
        }
        throw new IllegalArgumentException();
    }
}
1 @ClientQueryParam をインターフェイスに配置することで、クライアントのすべてのリクエストに my-param が追加されるようにします。 ${…​} 構文を使用したため、パラメーターの実際の値は my.property-value 設定プロパティを使用して取得されます。
2 getWithOtherParam を呼び出すと、クエリーパラメーター my-param に加え、 some-other-param に other の値が追加されます。
3 getFromMethod を呼び出すと、 my-param のクエリーパラメーターに加えて、 param-from-method に test の値 ( withParam のメソッドが param-from-method と呼び出されたときに返すため) が追加されます。

インターフェイスメソッドに @QueryParam のアノテーションが付いた引数がある場合、 その引数は @ClientQueryParam のアノテーションで指定されたものよりも優先されることに注意してください。

このアノテーションの詳細については、javadocに記載されています

パスパラメーター

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 を使用することを意味します。上記の設定を使用すると、 ExtensionsService の getById メソッドを 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

ホスト名の検証を無効にする

特定のRESTクライアントのSSLホスト名検証を無効にするには、次のプロパティーを構成に追加します。

quarkus.rest-client.extensions-api.verify-host=false

この設定は、SSLホスト名検証を無効にするため、本番運用では使用しないでください。

JAX-RSリソースの作成

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

package org.acme.rest.client;

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}")
    public Set<Extension> id(String id) {
        return extensionsService.getById(id);
    }
}

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

1 クライアントスタブには、通常の CDI @Inject ではなく、 @RestClient アノテーションが挿入されます。

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);
    }
}

カスタムHTTPオプションの使用

REST Client Reactive は、内部的に Vert.x HTTP クライアント を使用して、ネットワーク接続をします。REST Client Reactiveのエクステンションでは、プロパティを使っていくつか設定できます。例えば、

  • quarkus.rest-client.client-prefix.connect-timeout で、接続タイムアウトをミリ秒単位で設定します。

  • `quarkus.rest-client.client-prefix.max-redirects`でリダイレクトの回数を制限できます。

しかし、Vert.x HTTP Client 内には、接続を設定するための多くのオプションがあります。 このリンク のVert.x HTTP Client Options API ですべてのオプションを参照してください。

REST Client Reactive が内部で使用している Vert.x HTTP Client インスタンスを完全にカスタマイズするには、 カスタム HTTP Client Options インスタンスを CDI 経由またはプログラムによるクライアント作成時に変更できます。

ここでは、CDIを使用してHTTPクライアントオプションを提供する方法について、例を挙げて説明します:

package org.acme.rest.client;

import javax.enterprise.inject.Produces;
import javax.ws.rs.ext.ContextResolver;

import io.vertx.core.http.HttpClientOptions;
import io.quarkus.arc.Unremovable;

@Provider
public class CustomHttpClientOptions implements ContextResolver<HttpClientOptions> {

    @Override
    public HttpClientOptions getContext(Class<?> aClass) {
        HttpClientOptions options = new HttpClientOptions();
        // ...
        return options;
    }
}

これで、すべてのRESTクライアントが、あなたのカスタムHTTPクライアントオプションを使用するようになります。

もう一つの方法は、コードを用いてでクライアントを作成する際に、カスタムHTTPクライアントオプションを提供することです。

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;

import io.vertx.core.http.HttpClientOptions;

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

    private final ExtensionsService extensionsService;

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

    // ...
}

public class CustomHttpClientOptions implements ContextResolver<HttpClientOptions> {

    @Override
    public HttpClientOptions getContext(Class<?> aClass) {
        HttpClientOptions options = new HttpClientOptions();
        // ...
        return options;
    }
}
1 クライアントはCDI経由で提供されたHTTPクライアントオプションよりも、登録されたHTTPクライアントオプションを使用します。

テストの更新

次に、エンドポイントに加えられた変更を反映させるために、機能テストを更新する必要があります。 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"));
    }
}

上のコードでは、 REST Assured の json-path 機能を使用しています。

非同期サポート

クライアントのリアクティブな性質を最大限に活用するには、REST Client Reactive エクステンションのノンブロッキングフレーバーを使用するとよいでしょう。このエクステンションは CompletionStage と Uni をサポートしています。それでは、実際に 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 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}")
    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);
    }
}

呼び出しがノンブロッキングになったので、 idAsync メソッドがイベントループで呼び出されることに注意してください。つまり、ワーカープールのスレッドにオフロードされないので、ハードウェアリソースの使用量を減らすことができます。詳細は Resteasy reactive 実行モデル、ブロッキング、非ブロッキング を参照してください。

非同期メソッドをテストするには、 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 Client 仕様では、HTTP 応答を例外に変換することを目的とした org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper が導入されています。

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

public class MyResponseExceptionMapper implements 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-autodiscovery が false に設定されていない場合)。または、例外処理クラスを特定の REST クライアントインターフェイスにのみ適用する必要がある場合は、インターフェイスに @RegisterProvider(MyResponseExceptionMapper.class) でアノテーションを付けるか、適切な quarkus.rest-client 設定グループの providers プロパティー設定を使用して登録できます。

@ClientExceptionMapper を使用する

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

上記で定義された ExtensionsService REST Client インターフェイスの場合、 @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 属性が設定されておらず、すべてのハンドラーを呼び出す通常のルールが適用される場合、デフォルトの優先度を使用します。

Methods annotated with @ClientExceptionMapper can also take a java.lang.reflect.Method parameter which is useful if the exception mapping code needs to know the REST Client method that was invoked and caused the exception mapping code to engage.

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

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

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

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

データをマルチパートフォームとして送信するには、通常の @RestForm (または @FormParam ) アノテーションを使用します:

    @POST
    @Path("/binary")
    String sendMultipart(@RestForm File file, @RestForm String otherField);

File , Path , byte[] , Buffer として指定されたパラメータは、ファイルとして送信され、デフォルトで application/octet-stream MIME タイプになります。その他の @RestForm パラメータ型のデフォルトは、 text/plain MIMEタイプです。これらのデフォルトを上書きするには、 @PartType アノテーションを使用します。

当然ながら、これらのパラメーターを含むクラスにまとめられます:

    public static class Parameters {
        @RestForm
        File file;

        @RestForm
        String otherField;
    }

    @POST
    @Path("/binary")
    String sendMultipart(Parameters parameters);

File , Path , byte[] , Buffer , および @PartType のアノテーションが付いた @RestForm パラメータは、 @Consumes が存在しない場合、そのメソッドが @Consumes(MediaType.MULTIPART_FORM_DATA) を自動的に意味します。

If there are @RestForm parameters that are not multipart-implying, then @Consumes(MediaType.APPLICATION_FORM_URLENCODED) is implied.

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

また、 @PartType アノテーションを指定することで、JSON のマルチパートも送信できます。

    public static class Person {
        public String firstName;
        public String lastName;
    }

    @POST
    @Path("/json")
    String sendMultipart(@RestForm @PartType(MediaType.APPLICATION_JSON) Person person);

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

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 receiveMultipart();

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

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

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

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

The files you download are not automatically removed and can take up a lot of disk space. Consider removing the files when you are done working with them.

プロキシーサポート

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

  • グローバルクライアントプロキシー設定、 quarkus.rest-client.proxy-address、 quarkus.rest-client.proxy-user、 quarkus.rest-client.proxy-password、 quarkus.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 Client specification does not allow setting proxy credentials. In order to specify proxy user and proxy password programmatically, you need to cast your RestClientBuilder to RestClientBuilderImpl.

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

次のコマンドでアプリケーションを実行します。

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

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

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

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

そして、 java -jar target/quarkus-app/quarkus-run.jar で実行されます。

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

CLI
quarkus build --native
Maven
./mvnw install -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 is logging the traffic with level DEBUG and does not alter logger properties. You may need to adjust your logger configuration to use this feature.

ロギング設定の例:

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 のブログ投稿を参照してください。

Mocking does not work when using @NativeImageTest or @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 を使用して、モックを作成して挿入できます。

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

import static org.mockito.Mockito.when;

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.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;

@QuarkusTest
public class InjectMockTest {

    @InjectMock
    @RestClient
    Client mock;

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

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

QuarkusMock でモックする

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

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

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.LITERAL は installMockForType メソッドの最後の引数として渡される必要があることに注意してください

テストにモック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 の設定など、非ブロッキング実装には意味をなさないいくつかのことが機能しません