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レポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.git 、 アーカイブ をダウンロードします。
ソリューションは rest-client-reactive-quickstart ディレクトリ にあります。
Mavenプロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
このコマンドは、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 エクステンションを追加することができます。
quarkus extension add 'rest-client-reactive-jackson'
./mvnw quarkus:add-extension -Dextensions='rest-client-reactive-jackson'
./gradlew addExtension --extensions='rest-client-reactive-jackson'
これにより、 pom.xml に以下が追加されます:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
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 アノテーションです。
|
JSON のデフォルトに頼らない場合は、エンドポイントに |
|
上記の |
クエリーパラメーター
クエリーパラメーターを指定する最も簡単な方法は、クライアントメソッドのパラメーターに @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 と呼び出されたときに返すため) が追加されます。 |
|
インターフェースメソッドに |
このアノテーションの詳細については、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
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 クライアント仕様では、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 クライアントインターフェイスの場合、 @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 属性が設定されておらず、すべてのハンドラーを呼び出す通常のルールが適用される場合、デフォルトの優先度を使用します。
@ClientExceptionMapper でアノテーションされたメソッドは、 java.lang.reflect.Method パラメータを取れます。これは、例外対応のコードは、呼び出されて例外を発生したREST Client メソッドを知る場合に便利です。
|
マルチパートフォームのサポート
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) を自動的に意味します。
マルチパートではない @RestForm パラメータがある場合、 その時は @Consumes(MediaType.APPLICATION_FORM_URLENCODED) になります。
|
フォームデータのエンコードには、いくつかのモードがあります。デフォルトでは、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 のアノテーションを付ける必要があります。
| ダウンロードしたファイルは自動的に削除されず、多くのディスク領域を占有する可能性があります。作業が終わったら、ファイルを削除することを検討してください。 |
プロキシーサポート
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 クライアント仕様では、プロキシー資格情報の設定は許可されていません。プログラムでプロキシーユーザーとプロキシーパスワードを指定するには、 RestClientBuilder を RestClientBuilderImpl にキャストする必要があります。
|
アプリケーションをパッケージ化して実行する
アプリケーションを実行します:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
このエクステンションに関する基本的な情報を含む JSON オブジェクトが表示されるはずです。
いつものように、アプリケーションは以下の方法でパッケージ化されます。
quarkus build
./mvnw install
./gradlew build
そして、 java -jar target/quarkus-app/quarkus-run.jar で実行されます。
次のようにネイティブ実行可能ファイルを生成することもできます。
quarkus build --native
./mvnw install -Dnative
./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 を使用することです。
まず、以下の依存関係をアプリケーションに追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
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の設定など、非ブロッキング実装には意味をなさないいくつかのことが機能しません