Using the REST Client
このガイドでは、REST APIと対話するためのREST Clientの使用方法について説明します。 RESTクライアントは、Quarkus REST(旧RESTEasy Reactive)と互換性のあるRESTクライアントの実装です。
アプリケーションでクライアントを使用し、RESTエンドポイントを公開する場合は、サーバー部分に Quarkus REST を使用してください。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOMEが適切に設定されていること -
Apache Maven 3.9.8
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
git clone -b 3.15 https://github.com/quarkusio/quarkus-quickstarts.git で Git リポジトリーをクローンします。または、https://github.com/quarkusio/quarkus-quickstarts/archive/3.15.zip[アーカイブ] をダウンロードします。
解決策は rest-client-quickstart ディレクトリー にあります。
Mavenプロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-Dパラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=rest-client-quickstart"
このコマンドは、REST エンドポイントを持つ Maven プロジェクトを生成し、 rest-client と resteasy-jackson のエクステンションをインポートします。
-
REST サーバーのサポートには、
rest-jacksonエクステンションを使用してください。Jackson を使用したくない場合は、代わりにrestを使用してください; -
REST クライアントのサポートには
rest-client-jacksonエクステンションを使用してください。Jackson を使用したくない場合は、代わりにrest-clientを使用してください。
Quarkus プロジェクトがすでに設定されている場合は、プロジェクトのベースディレクトリーで次のコマンドを実行することで、
rest-client-jackson エクステンションをプロジェクトに追加できます。
quarkus extension add rest-client-jackson
./mvnw quarkus:add-extension -Dextensions='rest-client-jackson'
./gradlew addExtension --extensions='rest-client-jackson'
これにより、ビルドファイルに以下が追加されます:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-client-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 の使用は、適切な Jakarta REST および MicroProfile アノテーションを使用してインターフェイスを作成するのと同じくらい簡単です。今回の場合、インターフェイスは src/main/java/org/acme/rest/client/ExtensionsService.java で作成し、以下の内容を持つ必要があります。
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.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は、サービスへのアクセス方法を定義するために使用される標準的な Jakarta REST アノテーションです。
|
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 jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.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") String 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 と呼び出されたときに返すため) が追加されます。 |
|
インターフェイスメソッドに |
このアノテーションの詳細は、@ClientQueryParam の javadoc で確認できます。
フォームパラメーター
フォームパラメーターは、 @RestForm (または @FormParam) アノテーションを使用して指定できます。
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestForm;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Map;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
Set<Extension> postId(@FormParam("id") String id);
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
Set<Extension> postName(@RestForm String name);
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
Set<Extension> postFilter(@RestForm Map<String, String> filter);
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
Set<Extension> postFilters(@RestForm MultivaluedMap<String, String> filters);
}
@ClientFormParamの使用
フォームパラメーターは、 @ClientQueryParam と同様に @ClientFormParam を使用して指定することもできます:
@ClientFormParam(name = "my-param", value = "${my.property-value}")
public interface Client {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
String postWithParam();
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@ClientFormParam(name = "some-other-param", value = "other")
String postWithOtherParam();
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@ClientFormParam(name = "param-from-method", value = "{with-param}")
String postFromMethod();
default String withParam(String name) {
if ("param-from-method".equals(name)) {
return "test";
}
throw new IllegalArgumentException();
}
}
このアノテーションの詳細は @ClientFormParam のjavadocで確認できます。
パスパラメーター
GET リクエストにパスパラメーターが必要な場合は、 @QueryParam の代わりに (あるいは加えて) @PathParam ("parameter-name") アノテーションを使用することができます。
パスパラメーターとクエリーパラメーターは、必要に応じて以下の例のように組み合わせることができます。
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.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 という値で呼び出すと、 https://stage.code.quarkus.io/api/extensions?id=io.quarkus:quarkus-rest-client に 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=jakarta.inject.Singleton
ホスト名の検証を無効にする
特定のRESTクライアントのSSLホスト名検証を無効にするには、次のプロパティーを構成に追加します。
quarkus.rest-client.extensions-api.verify-host=false
|
この設定は、SSLホスト名検証を無効にするため、本番運用では使用しないでください。 |
HTTP/2 サポート
REST Clientでは、HTTP/2はデフォルトで無効になっています。有効にしたい場合は、次のように設定することができます:
// for all REST Clients:
quarkus.rest-client.http2=true
// or for a single REST Client:
quarkus.rest-client.extensions-api.http2=true
または、Application-Layer Protocol Negotiation (alpn) TLSエクステンションを有効にすると、クライアントは、サーバーが互換性のあるものよりも、どのHTTPバージョンを使用するかをネゴシエートすることができます。デフォルトでは、まずHTTP/2を使用しようとし、有効になっていない場合はHTTP/1.1を使用します。有効にしたい場合は、以下のように設定します:
quarkus.rest-client.alpn=true
// or for a single REST Client:
quarkus.rest-client.extensions-api.alpn=true
Jakarta RESTリソースの作成
src/main/java/org/acme/rest/client/ExtensionsResource.java ファイルを以下の内容で作成してください:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.ws.rs.GET;
import jakarta.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 アノテーションが挿入されます。 |
QuarkusRestClientBuilderを使用したプログラムによるクライアントの作成
クライアントに @RegisterRestClient アノテーションを付け、クライアントを @RestClient で注入する代わりに、
プログラムで REST Client を作成することもできます。
これは RestClientBuilder で行います。
このアプローチでは、クライアントインターフェイスは次のようになります。
package org.acme.rest.client;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.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 io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.net.URI;
import java.util.Set;
@Path("/extension")
public class ExtensionsResource {
private final ExtensionsService extensionsService;
public ExtensionsResource() {
extensionsService = QuarkusRestClientBuilder.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 は、内部的に Vert.x HTTP Client を使用して、ネットワーク接続をします。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 が内部的に使用している Vert.x HTTP Client インスタンスを完全にカスタマイズするには、CDI 経由で、またはプログラムでクライアントを作成するときに、HTTP Client Options インスタンスを提供します。
ここでは、CDIを使用してHTTPクライアントオプションを提供する方法について、例を挙げて説明します:
package org.acme.rest.client;
import jakarta.enterprise.inject.Produces;
import jakarta.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 io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import jakarta.ws.rs.GET;
import jakarta.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() {
HttpClientOptions options = new HttpClientOptions();
// ...
extensionsService = QuarkusRestClientBuilder.newBuilder()
.baseUri(URI.create("https://stage.code.quarkus.io/api"))
.httpClientOptions(options) (1)
.build(ExtensionsService.class);
}
// ...
}
| 1 | クライアントはCDI経由で提供されたHTTPクライアントオプションよりも、登録されたHTTPクライアントオプションを使用します。 |
リダイレクト
HTTPサーバーは、ステータスコードが "3 "で始まるレスポンスと、リダイレクト先のURLを保持するHTTPヘッダー "Location" を送信することで、レスポンスを別の場所にリダイレクトできます。RESTクライアントがHTTPサーバーからリダイレクトレスポンスを受信しても、新しい場所への再リクエストを自動的に実行することはありません。"follow-redirects"プロパティを追加することで、REST Clientで自動リダイレクトを有効にすることができます:
-
quarkus.rest-client.follow-redirectsで、すべてのRESTクライアントに対してリダイレクトが有効になります。 -
quarkus.rest-client.<client-prefix>.follow-redirectsで、特定のRESTクライアントに対するリダイレクトを有効にすることができます。
このプロパティがtrueの場合、REST Clientは、HTTPサーバーからリダイレクトレスポンスを受信すると、新しいリクエストを実行します。
さらに、プロパティ "max-redirects" を使って、リダイレクトの回数を制限することができます。
重要な注意点として、 RFC2616 仕様によると、デフォルトではリダイレクトは GET または HEAD メソッドに対してのみ実行されます。しかし、REST Client では、 @ClientRedirectHandler アノテーションを使用するか、CDI を利用するか、またはクライアント作成時にプログラム的に設定することで、POST や PUT メソッドのリダイレクトを有効にしたり、より複雑なロジックに従ってリダイレクトを行うカスタムリダイレクトハンドラーを指定できます。
@ClientRedirectHandler アノテーションを使用して独自のカスタムリダイレクトハンドラーを登録する方法の例を見てみましょう。
import jakarta.ws.rs.core.Response;
import io.quarkus.rest.client.reactive.ClientRedirectHandler;
@RegisterRestClient(configKey="extensions-api")
public interface ExtensionsService {
@ClientRedirectHandler
static URI alwaysRedirect(Response response) {
if (Response.Status.Family.familyOf(response.getStatus()) == Response.Status.Family.REDIRECTION) {
return response.getLocation();
}
return null;
}
}
"alwaysRedirect" リダイレクトハンドラーは、指定された REST Client (この例では "ExtensionsService" クライアント) によってのみ使用されます。
または、CDI 経由ですべての REST Client にカスタムリダイレクトハンドラーを提供することもできます。
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ContextResolver;
import jakarta.ws.rs.ext.Provider;
import org.jboss.resteasy.reactive.client.handlers.RedirectHandler;
@Provider
public class AlwaysRedirectHandler implements ContextResolver<RedirectHandler> {
@Override
public RedirectHandler getContext(Class<?> aClass) {
return response -> {
if (Response.Status.Family.familyOf(response.getStatus()) == Response.Status.Family.REDIRECTION) {
return response.getLocation();
}
// no redirect
return null;
};
}
}
これで、すべての REST Client が、お客様のカスタムリダイレクトハンドラーを使用するようになります。
別の方法として、クライアントを作成するときにプログラムで提供する方法があります。
@Path("/extension")
public class ExtensionsResource {
private final ExtensionsService extensionsService;
public ExtensionsResource() {
extensionsService = QuarkusRestClientBuilder.newBuilder()
.baseUri(URI.create("https://stage.code.quarkus.io/api"))
.register(AlwaysRedirectHandler.class) (1)
.build(ExtensionsService.class);
}
// ...
}
| 1 | クライアントは、CDI 経由で提供されたリダイレクトハンドラーよりも、登録されたリダイレクトハンドラーを使用します (存在する場合)。 |
テストの更新
次に、エンドポイントに加えられた変更を反映させるために、機能テストを更新する必要があります。
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")
.then()
.statusCode(200)
.body("$.size()", is(1),
"[0].id", is("io.quarkus:quarkus-rest-client"),
"[0].name", is("REST Client"),
"[0].keywords.size()", greaterThan(1),
"[0].keywords", hasItem("rest-client"));
}
}
上のコードでは、 REST Assured の json-path 機能を使用しています。
非同期サポート
クライアントのリアクティブな性質を最大限に活用するには、REST Client エクステンションのノンブロッキングフレーバーを使用します。
このエクステンションは CompletionStage と Uni をサポートしています。
それでは、実際に ExtensionsService REST インターフェイスに getByIdAsync メソッドを追加して、この動きを見てみましょう。コードは以下のようになります。
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.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 jakarta.ws.rs.GET;
import jakarta.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 メソッドがイベントループで呼び出されることに注意してください。
つまり、ワーカープールのスレッドにオフロードされないので、ハードウェアリソースの使用量を減らすことができます。
詳細は、Quarkus REST 実行モデル を参照してください。
非同期メソッドをテストするには、 ExtensionsResourceTest に以下のテストメソッドを追加します。
@Test
public void testExtensionIdAsyncEndpoint() {
given()
.when().get("/extension/id-async/io.quarkus:quarkus-rest-client")
.then()
.statusCode(200)
.body("$.size()", is(1),
"[0].id", is("io.quarkus:quarkus-rest-client"),
"[0].name", is("REST Client"),
"[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 jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.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 jakarta.ws.rs.GET;
import jakarta.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 の ドキュメント を参照してください。
サーバー送信イベント (SSE) のサポート
SSE イベントは、結果の型を io.smallrye.mutiny.Multi と宣言するだけで使用できます。
最も簡単な例
package org.acme.rest.client;
import io.smallrye.mutiny.Multi;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/sse")
@RegisterRestClient(configKey = "some-api")
public interface SseClient {
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
Multi<String> get();
}
|
SSE 結果のストリーミングに関係するすべての IO は、ノンブロッキング方式で実行されます。 |
結果は文字列に限定されません。たとえば、サーバーが各イベントの JSON ペイロードを返すと、Quarkus はそれを Multi で使用される汎用型に自動的にデシリアライズします。
|
ユーザーは、 イベントペイロードが
|
イベントのフィルタリング
場合によっては、SSE イベントのストリームに、クライアントから返されるべきではないイベントが含まれることがあります。この例としては、基盤となる TCP 接続を開いたままにするために、サーバーがハートビートイベントを送信することが挙げられます。
REST Client は、 @org.jboss.resteasy.reactive.client.SseEventFilter を提供することで、このようなイベントをフィルタリングすることをサポートします。
以下はハートビートイベントをフィルタリングする例です。
package org.acme.rest.client;
import io.smallrye.mutiny.Uni;
import java.util.function.Predicate;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.client.SseEvent;
import org.jboss.resteasy.reactive.client.SseEventFilter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@Path("/sse")
@RegisterRestClient(configKey = "some-api")
public interface SseClient {
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
@SseEventFilter(HeartbeatFilter.class)
Multi<SseEvent<Long>> get();
class HeartbeatFilter implements Predicate<SseEvent<String>> {
@Override
public boolean test(SseEvent<String> event) {
return !"heartbeat".equals(event.id());
}
}
}
カスタムヘッダーのサポート
REST 呼び出しのカスタムヘッダーを指定する方法はいくつかあります。
-
@RegisterClientHeadersアノテーションを用いてClientHeadersFactoryまたはReactiveClientHeadersFactoryを登録する方法。 -
QuarkusRestClientBuilder.clientHeadersFactory(factory)メソッドを使用して、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 jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import java.util.Set;
import io.quarkus.rest.client.reactive.NotBody;
@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)
@ClientHeaderParam(name = "header-from-method-param", value = "Bearer {token}") (5)
Set<Extension> getById(@QueryParam("id") String id, @HeaderParam("jaxrs-style-header") String headerValue, @NotBody String token); (6)
}
| 1 | クラスごとに存在できる ClientHeadersFactory は 1 つだけです。これを使用すると、カスタムヘッダーを追加できるだけでなく、既存のヘッダーを変換することもできます。ファクトリーの例については、以下の RequestUUIDHeaderFactory クラスを参照してください。 |
| 2 | @ClientHeaderParam は、クライアントインターフェイスとメソッドで使用できます。また、一定のヘッダー値を指定できます…。 |
| 3 | … また、ヘッダーの値を計算するメソッドの名前を指定することもできます。このメソッドは、静的メソッドまたはこのインターフェイス内のデフォルトメソッドのいずれかにすることができます。メソッドは、パラメーターなし、単一の String パラメーター、または単一の io.quarkus.rest.client.reactive.ComputedParamContext パラメーターを取ることができます (これは、メソッドのパラメーターに基づいてヘッダーを計算する必要があるコードに非常に便利で、 @io.quarkus.rest.client.reactive.NotBody と自然に補完し合います)。 |
| 4 | … また、アプリケーションの設定値も使用します。 |
| 5 | … または、逐語的なテキスト、メソッドパラメーター (名前で参照)、設定値 (前述のとおり)、メソッド呼び出し (前述のとおり) の任意の組み合わせでもかまいません。 |
| 6 | … または、通常の Jakarta REST `@HeaderParam`アノテーション付き引数として使用できます。 |
|
Kotlin を使用するときにデフォルトのメソッドを利用する場合は、Java のデフォルトのインターフェイス機能を使用するように Kotlin コンパイラーを設定する必要があります。 詳細は、こちら を参照してください。 |
ClientHeadersFactory は次のようになります。
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.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 jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.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;
});
}
}
|
HTTP Basic Auth を使用する場合、 以下に、非常に簡単な例を示します。
ここで、 |
デフォルトのヘッダーファクトリー
@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
リクエストのカスタマイズ
REST Client は、フィルターを介してサーバーに送信される最終リクエストのさらなるカスタマイズをサポートします。フィルターは、インターフェイス ClientRequestFilter または ResteasyReactiveClientRequestFilter のいずれかを実装する必要があります。
リクエストをカスタマイズする簡単な例として、カスタムヘッダーを追加することが挙げられます。
@Provider
public class TestClientRequestFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext requestContext) {
requestContext.getHeaders().add("my_header", "value");
}
}
次に、 @RegisterProvider アノテーションを使用してフィルターを登録できます。
@Path("/extensions")
@RegisterProvider(TestClientRequestFilter.class)
public interface ExtensionsService {
// ...
}
または、 .register() メソッドをプログラムで使用します。
QuarkusRestClientBuilder.newBuilder()
.register(TestClientRequestFilter.class)
.build(ExtensionsService.class)
Injecting the jakarta.ws.rs.ext.Providers instance in filters
jakarta.ws.rs.ext.Providers は、現在のクライアントのプロバイダー・インスタンスを検索する必要がある場合に便利です。
次のようにして、リクエストコンテキストからフィルター内の Providers インスタンスを取得できます。
@Provider
public class TestClientRequestFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext requestContext) {
Providers providers = ((ResteasyReactiveClientRequestContext) requestContext).getProviders();
// ...
}
}
または、 ClientRequestFilter インターフェイスの代わりに ResteasyReactiveClientRequestFilter インターフェイスを実装して、 ResteasyReactiveClientRequestContext コンテキストを直接提供することもできます。
@Provider
public class TestClientRequestFilter implements ResteasyReactiveClientRequestFilter {
@Override
public void filter(ResteasyReactiveClientRequestFilter requestContext) {
Providers providers = requestContext.getProviders();
// ...
}
}
REST Client Jackson の ObjectMapper のカスタマイズ
REST Client は、 @ClientObjectMapper アノテーションを使用して、クライアント専用のカスタム ObjectMapper を追加することをサポートします。
簡単な例として、次のようにして REST Client Jackson エクステンションにカスタム ObjectMapper を提供することが挙げられます。
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam("id") String id);
@ClientObjectMapper (1)
static ObjectMapper objectMapper(ObjectMapper defaultObjectMapper) { (2)
return defaultObjectMapper.copy() (3)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.UNWRAP_ROOT_VALUE);
}
}
| 1 | メソッドには @ClientObjectMapper アノテーションを付ける必要があります。 |
| 2 | これは静的メソッドである必要があります。また、 defaultObjectMapper パラメーターは CDI を介して解決されます。見つからない場合は、実行時に例外がスローされます。 |
| 3 | この例では、デフォルトのオブジェクトマッパーのコピーを作成しています。デフォルトのオブジェクトマッパーは 絶対に 変更しないでください。代わりにコピーを作成してください。 |
例外処理
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 Client によって自動的に使用されることはありません。アプリケーションのすべての REST Client で使用できるようにするには、クラスに @Provider アノテーションを付ける必要があります (quarkus.rest-client.provider-autodiscovery が false に設定されていない場合)。
または、例外処理クラスを特定の REST Client インターフェイスにのみ適用する必要がある場合は、インターフェイスに @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.
|
例外マッパーでの@Blockingアノテーションの使用
REST Client メソッドの戻り値の型として InputStream を使用する必要がある場合 (大量のデータを読み取る必要がある場合など) は、以下のようになります。
@Path("/echo")
@RegisterRestClient
public interface EchoClient {
@GET
InputStream get();
}
これは期待どおりに動作しますが、この InputStream オブジェクトをカスタム例外マッパーで読み取ろうとすると、 BlockingNotAllowedException 例外が発生します。これは、 ResponseExceptionMapper クラスがデフォルトでイベントループスレッドエグゼキューターで実行され、IO 操作を実行できないためです。
例外マッパーをブロッキングにするには、例外マッパーに @Blocking アノテーションを付けます。
@Provider
@Blocking (1)
public class MyResponseExceptionMapper implements ResponseExceptionMapper<RuntimeException> {
@Override
public RuntimeException toThrowable(Response response) {
if (response.getStatus() == 500) {
response.readEntity(String.class); (2)
return new RuntimeException("The remote service responded with HTTP 500");
}
return null;
}
}
| 1 | @Blocking アノテーションにより、MyResponseExceptionMapper例外マッパーはワーカースレッドプールで実行されることになります。 |
| 2 | ワーカースレッドプールでマッパーを実行しているため、エンティティの読み取りが許可されるようになりました。 |
なお、@ClientExceptionMapperを使用する場合は、 @Blocking アノテーションを使用することも可能です:
@Path("/echo")
@RegisterRestClient
public interface EchoClient {
@GET
InputStream get();
@ClientExceptionMapper
@Blocking
static RuntimeException toException(Response response) {
if (response.getStatus() == 500) {
response.readEntity(String.class);
return new RuntimeException("The remote service responded with HTTP 500");
}
return null;
}
}
マルチパートフォームのサポート
マルチパートメッセージの送信
REST Client を使用すると、データをマルチパートフォームで送信できます。このようにして、たとえば、 ファイルを効率的に送信できます。
データをマルチパートフォームとして送信するには、通常の @RestForm (または @FormParam ) アノテーションを使用します:
@POST
@Path("/binary")
String sendMultipart(@RestForm File file, @RestForm String otherField);
File、 Path、 byte[]、 Buffer、 FileUpload として指定されたパラメーターはファイルとして送信され、デフォルトでは
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);
@RestForm アノテーションが付けられた File、 Path、 byte[]、 Buffer、または FileUpload 型のパラメーター、
および @PartType アノテーションが付けられたパラメーターは、 @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 は RFC1738 を使用します。
クライアントレベルで、
io.quarkus.rest.client.multipart-post-encoder-mode RestBuilder プロパティーを
HttpPostRequestEncoder.EncoderMode の選択した値に設定するか、
application.properties で quarkus.rest-client.multipart-post-encoder-mode を指定して、
モードをオーバーライドできます。後者は @RegisterRestClient アノテーションで作成された
クライアントに対してのみ機能することに注意してください。
利用可能なすべてのモードは、Netty ドキュメント に記載されています。
また、 @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 は次のように REST Client で使用できる ClientMultipartForm を提供します。
public interface MultipartService {
@POST
@Path("/multipart")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
Map<String, String> multipart(ClientMultipartForm dataParts);
}
このクラスとサポートされているメソッドの詳細は、ClientMultipartForm の javadoc を参照してください。
受信したマルチパートオブジェクトをクライアントリクエストに変換する
ClientMultipartForm を作成する良い例は、サーバーの MultipartFormDataInput (Quarkus REST によって受信されたマルチパートリクエストを表す) から作成されるものです。その目的は、任意の変更を許可しながらリクエストをダウンストリームに伝播することです。
public ClientMultipartForm buildClientMultipartForm(MultipartFormDataInput inputForm) (1)
throws IOException {
ClientMultipartForm multiPartForm = ClientMultipartForm.create(); (2)
for (Entry<String, Collection<FormValue>> attribute : inputForm.getValues().entrySet()) {
for (FormValue fv : attribute.getValue()) {
if (fv.isFileItem()) {
final FileItem fi = fv.getFileItem();
String mediaType = Objects.toString(fv.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE),
MediaType.APPLICATION_OCTET_STREAM);
if (fi.isInMemory()) {
multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(),
Buffer.buffer(IOUtils.toByteArray(fi.getInputStream())), mediaType); (3)
} else {
multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(),
fi.getFile().toString(), mediaType); (4)
}
} else {
multiPartForm.attribute(attribute.getKey(), fv.getValue(), fv.getFileName()); (5)
}
}
}
return multiPartForm;
}
| 1 | MultipartFormDataInput は、受信したマルチパートリクエストを表す Quarkus REST (Server) 型です。 |
| 2 | ClientMultipartForm が作成されました。 |
| 3 | メモリー内のファイル属性を表すリクエスト属性に対して FileItem 属性が作成されます。 |
| 4 | FileItem 属性は、ファイルシステムに保存されたファイル属性を表すリクエスト属性に対して作成されます。 |
| 5 | FileItem でない場合は、ファイル以外の属性が ClientMultipartForm に直接追加されます。 |
同様に、受信したサーバーのマルチパート要求が既知で、次のようになる場合、
public class Request { (1)
@RestForm("files")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
List<FileUpload> files;
@RestForm("jsonPayload")
@PartType(MediaType.TEXT_PLAIN)
String jsonPayload;
}
ClientMultipartForm は次のように簡単に作成できます。
public ClientMultipartForm buildClientMultipartForm(Request request) { (1)
ClientMultipartForm multiPartForm = ClientMultipartForm.create();
multiPartForm.attribute("jsonPayload", request.getJsonPayload(), "jsonPayload"); (2)
request.getFiles().forEach(fu -> {
multiPartForm.fileUpload(fu); (3)
});
return multiPartForm;
}
| 1 | サーバー部分が受け入れるリクエストを表す Request |
| 2 | jsonPayload 属性が ClientMultipartForm に直接追加されます。 |
| 3 | リクエストの FileUpload から fileUpload が作成されます。 |
|
同じ名前を使用するマルチパートデータを送信する場合、クライアントとサーバーが同じマルチパートエンコーダーモードを使用していない場合、問題が発生する可能性があります。
デフォルトでは、REST Client は この設定は、 |
マルチパートメッセージの受信
REST Client は、マルチパートメッセージの受信もサポートしています。 送信と同様に、マルチパートレスポンスを解析するには、レスポンスデータを記述するクラスを作成する必要があります。以下に例を示します。
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 は、マルチパートの戻り値の型として使用されるクラスを事前に認識する必要があります。 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. |
マルチパート混合/OData の使用状況
アプリケーションが、 OData と呼ばれる特別なプロトコルを使用してエンタープライズシステム (CRM システムなど) と対話する必要があることは珍しくありません。
このプロトコルは基本的に、REST Client で動作するためにいくつかのグルーコードを必要とするカスタム HTTP Content-Type を使用します (本体の作成は完全にアプリケーション次第であり、REST Client はあまり役に立ちません)。
例は次のようになります:
@Path("/crm")
@RegisterRestClient
public interface CRMService {
@POST
@ClientHeaderParam(name = "Content-Type", value = "{calculateContentType}") (1)
String performBatch(@HeaderParam("Authorization") String accessToken, @NotBody String batchId, String body); (2)
default String calculateContentType(ComputedParamContext context) {
return "multipart/mixed;boundary=batch_" + context.methodParameters().get(1).value(); (3)
}
}
コードでは次の部分を使用します。
| 1 | @ClientHeaderParam(name = "Content-Type", value = "{calculateContentType}") は、インターフェイスの calculateContentType デフォルトメソッドを呼び出して Content-Type ヘッダーが作成されるようにします。 |
| 2 | 前述のパラメーターは、HTTP ヘッダーの構築を支援するためにのみ使用されるため、 @NotBody アノテーションを付ける必要があります。 |
| 3 | context.methodParameters().get(1).value() により、 calculateContentType メソッドは REST Client メソッドに渡される適切なメソッドパラメーターを取得できるようになります。 |
前述したように、サービスの要件に準拠するには、アプリケーションコードによって body パラメーターを適切に作成する必要があります。
プロキシーサポート
REST Client はプロキシ経由でのリクエスト送信をサポートしています。 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.
|
開発モードのローカルプロキシー
開発モードで REST クライアントを使用する際、Quarkus はパススループロキシーを立ち上げる機能を備えており、これを Wireshark (または類似のツール) のターゲットとして使用することで、 REST クライアントから発信されるすべてのトラフィックをキャプチャーすることができます (特に REST Client が HTTPS サービスに対して使用される場合に有用です)。
この機能は、プロキシーが必要なクライアントに対応する configKey の enable-local-proxy 設定オプションを設定するだけで有効化できます。
以下に例を示します。
quarkus.rest-client.my-client.enable-local-proxy=true
REST クライアントが設定キーを使用しない場合 (たとえば、 QuarkusRestClientBuilder を介してプログラムで作成される場合など)、代わりにクラス名を使用できます。
以下に例を示します。
quarkus.rest-client."org.acme.SomeClient".enable-local-proxy=true
プロキシーがリッスンしているポートは、起動ログで確認できます。エントリーの例は次のとおりです。
Started HTTP proxy server on http://localhost:38227 for REST Client 'org.acme.SomeClient'
アプリケーションをパッケージ化して実行する
次のコマンドでアプリケーションを実行します。
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.native.enabled=true
トラフィックの記録
REST Client は、送信したリクエストと受信したレスポンスをログに記録できます。
ロギングを有効にするには、 application.properties に quarkus.rest-client.logging.scope プロパティを追加し、次のように設定します:
-
リクエストと応答の内容をログに記録する
request-response、または -
allは、基礎となるライブラリーの低レベルのロギングも有効にします。
HTTP メッセージには大きな本文が含まれる可能性があるため、ログに記録される本文の文字数を制限します。デフォルトの制限は 100 ですが、 quarkus.rest-client.logging.body-limit を指定することで変更できます。
| REST Client 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
|
REST Client はデフォルトの
|
テストのためのクライアントのモッキング
@RestClient アノテーションが挿入されたクライアントを使用する場合は、テスト用に簡単にモックすることができます。
Mockito の @InjectMock または QuarkusMock でそれを行うことができます。
このセクションでは、クライアントをモックに置き換える方法を示します。Quarkus でモッキングがどのように機能するかをより深く理解したい場合は、 Mocking CDI beans のブログ投稿を参照してください。
Mocking does not work when using @QuarkusIntegrationTest.
|
次のクライアントがあると仮定します。
package io.quarkus.it.rest.client.main;
import jakarta.ws.rs.GET;
import jakarta.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.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
@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 や Hoverfly があります。
このセクションでは、ここまでで開発した ExtensionsService をテストするために Wiremock を活用する方法を示します。
まず、Wiremockをテストの依存関係として追加する必要があります。Mavenプロジェクトの場合は以下のようになります:
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
<version>${wiremock.version}</version> (1)
</dependency>
| 1 | 適切なWiremockバージョンを使用してください。利用可能なすべてのバージョンは、 こちら をご覧ください。 |
testImplementation("org.wiremock:wiremock:$wiremockVersion") (1)
| 1 | 適切なWiremockバージョンを使用してください。利用可能なすべてのバージョンは、 こちら をご覧ください。 |
Quarkusのテストを実行する前にサービスを開始する必要がある場合、Quarkusのテストでは、 @io.quarkus.test.common.QuarkusTestResource アノテーションを利用して、サービスを開始できる io.quarkus.test.common.QuarkusTestResourceLifecycleManager を指定し、Quarkusが使用する設定値を提供します。
|
|
WiremockExtensions という名前で、 QuarkusTestResourceLifecycleManager の実装を以下のように作成します。
package org.acme.rest.client;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import static com.github.tomakehurst.wiremock.client.WireMock.*; (1)
public class WireMockExtensions implements QuarkusTestResourceLifecycleManager { (2)
private WireMockServer wireMockServer;
@Override
public Map<String, String> start() {
wireMockServer = new WireMockServer();
wireMockServer.start(); (3)
wireMockServer.stubFor(get(urlEqualTo("/extensions?id=io.quarkus:quarkus-rest-client")) (4)
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(
"[{" +
"\"id\": \"io.quarkus:quarkus-rest-client\"," +
"\"name\": \"REST Client\"" +
"}]"
)));
wireMockServer.stubFor(get(urlMatching(".*")).atPriority(10).willReturn(aResponse().proxiedFrom("https://stage.code.quarkus.io/api"))); (5)
return Map.of("quarkus.rest-client.\"org.acme.rest.client.ExtensionsService\".url", wireMockServer.baseUrl()); (6)
}
@Override
public void stop() {
if (null != wireMockServer) {
wireMockServer.stop(); (7)
}
}
}
| 1 | Wiremock パッケージのメソッドを静的にインポートすることで、テストが読みやすくなります。 |
| 2 | start メソッドは、テストを実行する前にQuarkusによって呼び出され、テスト実行中に適用される設定プロパティーの Map を返します。 |
| 3 | Wiremockを起動します。 |
| 4 | /extensions?id=io.quarkus:quarkus-rest-client への呼び出しに対して特定のレスポンスを返すよう、Wiremock のスタブを設定します。 |
| 5 | スタブ化されていないすべての HTTP 呼び出しは、実際のサービスを呼び出すことで処理されます。これはデモンストレーションを目的として行われているものであり、通常は実際のテストでは発生しません。 |
| 6 | start メソッドはテストに適用される設定を返すので、 ExtensionsResource の実装で使用されるベース URL を制御する rest-client プロパティーを、
Wiremock がリクエストの着信をリッスンするベース URL に設定します。 |
| 7 | すべてのテストが終了したら、Wiremockをシャットダウンします。 |
ExtensionsResourceTest クラスには、以下のようなアノテーションが必要です。
@QuarkusTest
@QuarkusTestResource(WireMockExtensions.class)
public class ExtensionsResourceTest {
}
|
|
既知の制限
REST Client エクステンションは、REST Client エクステンションのドロップインリプレースメントを目的としていますが、いくつかの違い や制限があります。
-
新しいエクステンションのクライアントのデフォルトスコープは
@ApplicationScopedですが、quarkus-resteasy-clientのデフォルトは@Dependentです。 この動作を変更するには、quarkus.rest-client.scopeプロパティーを完全修飾形式のスコープ名に設定します。 -
HostnameVerifierまたはSSLContextを設定することができません -
ExecutorServiceの設定など、非ブロッキング実装には意味をなさないいくつかのことが機能しません