REST クライアントの利用
|
このガイドは、 Quarkus 2.8 までデフォルトの 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.8.6
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.git 、 アーカイブ をダウンロードします。
ソリューションは rest-client-quickstart directory にあります。
Mavenプロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
このコマンドは、REST エンドポイントを持つ Maven プロジェクトを生成し、 rest-client と resteasy-jackson のエクステンションをインポートします。
-
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 の利用は、適切な 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 org.jboss.resteasy.annotations.jaxrs.QueryParam;
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 String id);
}
getById メソッドは、私たちのコードに、Code Quarkus API からidを指定してエクステンションを問い合わせる機能を与えます。クライアントがすべてのネットワーキングとマーシャリングを処理してくれるので、このような技術的な詳細は一切ありません。
上のコードのアノテーションの目的は以下の通りです。
-
@RegisterRestClientにより、Quarkusは、このインターフェイスがRESTクライアントとしてCDIインジェクションに利用可能であることを知ることができます。 -
@Pathと@GET、@QueryParamはサービスへのアクセス方法を定義するために使用される標準的な JAX-RS アノテーションです。
|
デフォルトで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 javax.ws.rs.GET;
import javax.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=javax.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=javax.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 クライアントは、 |
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 org.jboss.resteasy.annotations.jaxrs.PathParam;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.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 機能を使用しています。
非同期サポート
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 javax.ws.rs.GET;
import javax.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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.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 javax.ws.rs.GET;
import javax.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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.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 javax.ws.rs.GET;
import javax.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 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にすることができます。
デフォルトのヘッダーファクトリー
また、カスタムファクトリーを指定せずに @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つは、 JAX-RS の意味においての同じプロバイダーリストを共有していることです。
例えば、 WriterInterceptor を宣言した場合、デフォルトではサーバーの呼び出しとクライアントの呼び出しの両方をインターセプトしますが、これは望ましい動作ではないかもしれません。
しかし、このデフォルトの動作を変更してプロバイダを制約することができます。
-
プロバイダに
@ConstrainedTo(RuntimeType.CLIENT)アノテーションを追加することで、 クライアント コールのみを考慮します。 -
プロバイダに
@ConstrainedTo(RuntimeType.SERVER)アノテーションを追加することで、 サーバー コールのみを考慮します。
テストにモックHTTPサーバーを使用する
テストを実行する際にモック HTTP サーバーを設定することは、一般的なテストパターンです。そのようなサーバーの例としては、 Wiremock や Hoverfly があります。このセクションでは、ここまでで開発した ExtensionsService をテストするために Wiremock を活用する方法を示します。
最初に、 Wiremock をテストの依存関係として追加する必要があります。 Maven プロジェクトの場合は以下のようになります。
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<scope>test</scope>
<version>${wiremock.version}</version> (1)
</dependency>
| 1 | 適切なWiremockのバージョンを使用してください。利用可能なすべてのバージョンは ここ で 見つけることができます。 |
testImplementation("com.github.tomakehurst:wiremock-jre8:$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.Collections;
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 Collections.singletonMap("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 {
}
|
|