レガシーREST クライアントの利用
このガイドは、Quarkus 2.8までデフォルトのJakarta REST(旧称JAX-RS)実装であった RESTEasy Classic と互換性のあるREST Clientに関するものです。 現在では、従来のブロック型ワークロードとリアクティブ型ワークロードを同様にサポートするRESTEasy Reactiveの使用が推奨されています。RESTEasy Reactiveの詳細については、 REST Client Reactiveガイド、サーバーサイドについては、 REST JSON入門ガイド、またはより詳細な RESTEasy Reactiveガイドを参照してください。 |
このガイドでは、MicroProfile REST Clientを使用して、ほとんど手間をかけずにREST APIとやりとりする方法を説明します。
サーバーサイドで JSON REST API を書く必要がある場合は、JSON REST API ガイドを参照してください。 |
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 11+ がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.5
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
git clone https://github.com/quarkusio/quarkus-quickstarts.git
で Git レポジトリをクローンするか、 アーカイブ をダウンロードします。
ソリューションは rest-client-quickstart
ディレクトリ にあります。
Maven プロジェクトの作成
最初に、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=rest-client-quickstart"
このコマンドは、 REST エンドポイントを持つ Maven プロジェクトを生成し、以下をインポートします。
-
REST サーバーのサポートのための
resteasy
とresteasy-jackson
のエクステンション。 -
REST クライアントのサポートのための
rest-client
とrest-client-jackson
のエクステンション。
すでにQuarkusプロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、 rest-client
と rest-client-jackson
のエクステンションをプロジェクトに追加することができます。
quarkus extension add rest-client,rest-client-jackson
./mvnw quarkus:add-extension -Dextensions='rest-client,rest-client-jackson'
./gradlew addExtension --extensions='rest-client,rest-client-jackson'
これにより、 pom.xml
に以下の内容が追加されます。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-client")
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;
}
上記のモデルは、サービスによって提供されるフィールドのサブセットに過ぎませんが、このガイドの目的には十分です。
インターフェースの作成
RESTEasy 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 org.jboss.resteasy.annotations.jaxrs.QueryParam;
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 String id);
}
getById
メソッドは、 ID を指定して Code Quarkus API からエクステンションを取得する機能を提供します。クライアントがすべてのネットワークとマーシャリングを処理するため、コードにそのような技術的な詳細を記述する必要はありません。
上記のコードのアノテーションの目的は以下の通りです。
-
@RegisterRestClient
により、Quarkusは、このインターフェイスがRESTクライアントとしてCDIインジェクションに利用可能であることを知ることができます。 -
@Path
と@GET
、@QueryParam
は、サービスへのアクセス方法を定義するために使用される標準的な Jakarta REST アノテーションです。
デフォルトでJSONを使用したくない場合は、 JSONのデフォルトに依存しない場合は、エンドポイントに |
パスパラメーター
GET リクエストにパスパラメーターが必要な場合は、 @QueryParam
の代わりに、または追加で、 @PathParam("parameter-name")
を利用することができます。パスとクエリパラメーターは、必要に応じて組み合わせることができます。以下のモックの例を参照してください。
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
@Path("/stream/{stream}")
Set<Extension> getByStream(@PathParam 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)
quarkus.rest-client."org.acme.rest.client.ExtensionsService".scope=jakarta.inject.Singleton # (2)
1 | この設定をすると、 ExtensionsService を使用して実行されるすべてのリクエストが https://stage.code.quarkus.io をベースURLとして使用します。上記の設定を使用して、 ExtensionsService の getById メソッドを io.quarkus:quarkus-rest-client の値で呼び出すと、HTTP GET リクエストが https://stage.code.quarkus.io/api/extensions?id=io.quarkus:quarkus-rest-client に対して実行されます。 |
2 | この設定をすると、 ExtensionsService のデフォルトのスコープは @Singleton になります。サポートされているスコープの値は @Singleton , @Dependent , @ApplicationScoped および @RequestScoped です。既定のスコープは @Dependent です。デフォルトのスコープはインターフェイス上で定義することもできます。 |
org.acme.rest.client.ExtensionsService
は、前のセクションで作成した ExtensionsService
インターフェースの完全修飾名と 一致しなければならない ことに注意してください。
クライアントを設定の設定には、標準の MicroProfile Rest Client のプロパティー記法を使用することもできます。
プロパティーが Quarkus 記法と MicroProfile 記法の両方で指定されている場合、 Quarkus 記法が優先されます。 |
設定を容易にするために、 @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 ホスト名検証を無効にするため、実運用環境では使用しないでください。 |
さらに、カスタムのホスト名検証ストラテジーを使用するようにRESTクライアントを設定することができます。必要なのは、インターフェース javax.net.ssl.HostnameVerifier
を実装したクラスを用意し、以下のプロパティを設定に追加することだけです。
quarkus.rest-client.extensions-api.hostname-verifier=<full qualified custom hostname verifier class name>
Quarkus REST クライアントは、 |
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 org.jboss.resteasy.annotations.jaxrs.PathParam;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;
@Path("/extension")
public class ExtensionsResource {
@Inject
@RestClient
ExtensionsService extensionsService;
@GET
@Path("/id/{id}")
public Set<Extension> id(@PathParam String id) {
return extensionsService.getById(id);
}
}
標準の CDI @Inject
アノテーションに加えて、 ExtensionsService
を注入するために MicroProfile @RestClient
アノテーションを使用する必要があることに注意してください。
テストの更新
また、エンドポイントに加えられた変更を反映させるために、機能テストを更新する必要があります。 src/test/java/org/acme/rest/client/ExtensionsResourceTest.java
ファイルを編集し、 testExtensionIdEndpoint
メソッドの内容を変更します。
package org.acme.rest.client;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.greaterThan;
import org.acme.rest.client.resources.WireMockExtensionsResource;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
@QuarkusTestResource(WireMockExtensionsResource.class)
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 Classic"),
"[0].keywords.size()", greaterThan(1),
"[0].keywords", hasItem("rest-client"));
}
}
上記のコードでは、 REST Assured の json-path 機能を使用しています。
リダイレクト
HTTPサーバーは、ステータスコードが "3 "で始まるレスポンスと、リダイレクト先のURLを保持するHTTPヘッダー "Location "を送信することで、レスポンスを別の場所にリダイレクトできます。RESTクライアントがHTTPサーバーからリダイレクトレスポンスを受信しても、新しい場所への再リクエストは自動的に実行されません。ただし、"follow-redirects "プロパティを有効にすることで、自動リダイレクトを有効にすることができます:
-
quarkus.rest-client.follow-redirects
を有効にすると、すべてのRESTクライアントに対してリダイレクトが有効になります。 -
quarkus.rest-client.<client-prefix>.follow-redirects
を有効にすると、特定のRESTクライアントに対するリダイレクトを有効にすることができます。
このプロパティがtrueの場合、REST Clientは、HTTPサーバーからリダイレクトレスポンスを受信すると、新しいリクエストを実行します。
さらに、プロパティ "max-redirects" を使って、リダイレクトの回数を制限することができます。
RFC2616 仕様によると、デフォルトではGETまたはHEADメソッドに対してのみリダイレクトが行われるという重要な注意点があります。
非同期のサポート
rest クライアントは非同期の rest 呼び出しをサポートしています。非同期のサポートには、2パターンあります。 CompletionStage
を返却するか Uni
( quarkus-rest-client-mutiny
のエクステンションが必要です) を返却するかです。 ExtensionsService
REST インタフェースに getByIdAsync
メソッドを追加してみましょう。コードは以下のようになります。
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam String id);
}
src/main/java/org/acme/rest/client/ExtensionsResource.java
ファイルを開き、以下の内容で更新します:
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
@Path("/extension")
public class ExtensionsResource {
@Inject
@RestClient
ExtensionsService extensionsService;
@GET
@Path("/id/{id}")
public Set<Extension> id(@PathParam String id) {
return extensionsService.getById(id);
}
@GET
@Path("/id-async/{id}")
public CompletionStage<Set<Extension>> idAsync(@PathParam String id) {
return extensionsService.getByIdAsync(id);
}
}
非同期メソッドをテストするには、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 Classic"),
"[0].keywords.size()", greaterThan(1),
"[0].keywords", hasItem("rest-client"));
}
Uni
版は、非常に似ています。
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import io.smallrye.mutiny.Uni;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
// ...
@GET
Uni<Set<Extension>> getByIdAsUni(@QueryParam String id);
}
ExtensionsResource
は以下ようになります。
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import io.smallrye.mutiny.Uni;
@Path("/extension")
public class ExtensionsResource {
@Inject
@RestClient
ExtensionsService extensionsService;
// ...
@GET
@Path("/id-uni/{id}")
public Uni<Set<Extension>> idMutiny(@PathParam String id) {
return extensionsService.getByIdAsUni(id);
}
}
Mutiny
前のスニペットでは、Mutinyのリアクティブ型を使用していますが、もし慣れていない場合は、まず Mutiny - 直感的なリアクティブプログラミングライブラリ を読んでください。 |
Uni
を返すとき、すべての サブスクリプション は 、リモートサービスを呼び出します。つまり、 Uni
で再購読してリクエストを再送信するか、以下のように retry
を使用することができます。
@Inject @RestClient ExtensionsService extensionsService;
// ...
extensionsService.getByIdAsUni(id)
.onFailure().retry().atMost(10);
CompletionStage
を使用する場合は、再試行のためにサービスのメソッドを呼び出す必要があります。この違いは、 Mutiny とそのサブスクリプションプロトコルの lazy (遅延) 性の側面から来ています。これについての詳細は Mutiny の ドキュメント を参照してください。
カスタムヘッダーのサポート
MicroProfile REST クライアントでは、 ClientHeadersFactory
を @RegisterClientHeaders
アノテーションで登録することで、リクエストヘッダーを修正することができます。
ExtensionsResource
REST インタフェースに @RegisterClientHeaders
アノテーションを追加して、 RequestUUIDHeaderFactory
クラスを指すようにします。
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import io.smallrye.mutiny.Uni;
@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders(RequestUUIDHeaderFactory.class)
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam String id);
@GET
Uni<Set<Extension>> getByIdAsUni(@QueryParam String id);
}
そして、 RequestUUIDHeaderFactory
は次のようになります。
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にすることができます。
デフォルトのヘッダーファクトリー
また、カスタムファクトリーを指定せずに @RegisterClientHeaders
アノテーションを使用することもできます。その場合は、 DefaultClientHeadersFactoryImpl
ファクトリーが使用され、 org.eclipse.microprofile.rest.client.propagateHeaders
プロパティーに記載されているすべてのヘッダーが修正されます。個々のヘッダー名はコンマで区切られています。
@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam String id);
@GET
Uni<Set<Extension>> getByIdAsUni(@QueryParam String id);
}
org.eclipse.microprofile.rest.client.propagateHeaders=Authorization,Proxy-Authorization
アプリケーションのパッケージ化と実行
アプリケーションを以下のように実行します。
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
REST Client extension に関する基本情報を含む 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 クライアントと RESTEasy のやりとり
Quarkusでは、REST Clientエクステンションと RESTEasyエクステンション は、同じインフラストラクチャを共有しています。この考慮の重要な結果の1つは、(Jakarta RESTの意味での)プロバイダのリストを共有することです。
例えば、 WriterInterceptor
を宣言した場合、デフォルトではサーバーの呼び出しとクライアントの呼び出しの両方をインターセプトしますが、これは望ましい動作ではないかもしれません。
しかし、このデフォルトの動作を変更してプロバイダーに制約を設けることができます。
-
プロバイダーに
@ConstrainedTo(RuntimeType.CLIENT)
アノテーションを追加することで、 クライアント の呼び出しのみを考慮します。 -
プロバイダーに
@ConstrainedTo(RuntimeType.SERVER)
アノテーションを追加することで、 サーバー の呼び出しのみを考慮します。
テスト時のモック 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 Classic\"" +
"}]"
)));
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 {
}
|