Spring 5 WebClient
1. 概要
この記事では、Spring 5で導入されているリアクティブWebクライアントであるWebClientを紹介します。
WebTestClientも見ていきます。これはテストで使用するように設計されたWebClientです。
参考文献:
2. WebClientとは何ですか?
簡単に言うと、WebClientは、Web要求を実行するための主要なエントリポイントを表すインターフェイスです。
これはSpringWeb Reactiveモジュールの一部として作成されており、これらのシナリオで従来のRestTemplateを置き換えます。 新しいクライアントは、HTTP / 1.1プロトコルを介して機能する事後対応型のノンブロッキングソリューションです。
最後に、インターフェースには1つの実装(DefaultWebClientクラス)があり、これを使用します。
3. 依存関係
Spring Bootアプリケーションを使用しているため、spring-boot-starter-webflux依存関係とthe Reactor project.が必要です。
3.1. Mavenを使用した構築
次の依存関係をpom.xmlファイルに追加しましょう。
org.springframework.boot
spring-boot-starter-webflux
org.projectreactor
reactor-spring
1.0.1.RELEASE
3.2. Gradleを使用した構築
Gradleを使用して、次のエントリをbuild.gradleファイルに追加する必要があります。
dependencies {
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.projectreactor:reactor-spring:1.0.1.RELEASE'
}
4. WebClientの操作
クライアントと適切に連携するには、次の方法を知る必要があります。
-
インスタンスを作成する
-
リクエストをする
-
応答を処理する
4.1. WebClientインスタンスの作成
選択肢は3つあります。 1つ目は、デフォルト設定でWebClientオブジェクトを作成することです。
WebClient client1 = WebClient.create();
2番目の方法では、指定されたベースURIを使用してWebClientインスタンスを開始できます。
WebClient client2 = WebClient.create("http://localhost:8080");
最後の方法(および最も高度な方法)は、DefaultWebClientBuilderクラスを使用してクライアントを構築することです。これにより、完全なカスタマイズが可能になります。
WebClient client3 = WebClient
.builder()
.baseUrl("http://localhost:8080")
.defaultCookie("cookieKey", "cookieValue")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
.build();
4.2. リクエストの準備
まず、method(HttpMethod method)を呼び出すか、get、post、deleteなどのショートカットメソッドを呼び出して、リクエストのHTTPメソッドを指定する必要があります。
WebClient.UriSpec request1 = client3.method(HttpMethod.POST);
WebClient.UriSpec request2 = client3.post();
次の動きはURLを提供することです。 これをuri APIに渡すことができます–Stringまたはjava.net.URLインスタンスとして:
WebClient.RequestBodySpec uri1 = client3
.method(HttpMethod.POST)
.uri("/resource");
WebClient.RequestBodySpec uri2 = client3
.post()
.uri(URI.create("/resource"));
次に、必要に応じて、リクエストの本文、コンテンツタイプ、長さ、Cookieまたはヘッダーを設定できます。
たとえば、リクエスト本文を設定する場合、2つの方法があります。BodyInserterで埋めるか、この作業をPublisherに委任します。
WebClient.RequestHeadersSpec requestSpec1 = WebClient
.create()
.method(HttpMethod.POST)
.uri("/resource")
.body(BodyInserters.fromPublisher(Mono.just("data")), String.class);
WebClient.RequestHeadersSpec> requestSpec2 = WebClient
.create("http://localhost:8080")
.post()
.uri(URI.create("/resource"))
.body(BodyInserters.fromObject("data"));
The BodyInserter is an interface responsible for populating a ReactiveHttpOutputMessage body with a given output message and a context used during the insertion.Publisherは、潜在的に無制限の数のシーケンスされた要素を提供することを担当する反応性コンポーネントです。
2番目の方法はbodyメソッドです。これは、元のbody(BodyInserter inserter)メソッドのショートカットです。
BodyInserter,を埋めるこのプロセスを軽減するために、BodyInsertersクラスにいくつかの便利なユーティリティメソッドがあります。
BodyInserter, ReactiveHttpOutputMessage> inserter1 = BodyInserters
.fromPublisher(Subscriber::onComplete, String.class);
MultiValueMapでも可能です:
LinkedMultiValueMap map = new LinkedMultiValueMap();
map.add("key1", "value1");
map.add("key2", "value2");
BodyInserter inserter2
= BodyInserters.fromMultipartData(map);
または、単一のオブジェクトを使用して:
BodyInserter
ボディを設定したら、ヘッダー、Cookie、受け入れ可能なメディアタイプを設定できます。 Values will be added to those have been set when instantiating the client.
また、“If-None-Match”, “If-Modified-Since”, “Accept”, “Accept-Charset”.のような最も一般的に使用されるヘッダーの追加サポートがあります
これらの値の使用方法の例を次に示します。
WebClient.ResponseSpec response1 = uri1
.body(inserter3)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.acceptCharset(Charset.forName("UTF-8"))
.ifNoneMatch("*")
.ifModifiedSince(ZonedDateTime.now())
.retrieve();
4.3. 応答を得る
最終段階では、リクエストを送信し、レスポンスを受信します。 これは、exchangeまたはretrieveメソッドのいずれかを使用して実行できます。
それらは戻り値の型が異なります。 exchangeメソッドは、ステータス、ヘッダーとともにClientResponseを提供しますが、retrieveメソッドは、本体を直接フェッチするための最短パスです。
String response2 = request1.exchange()
.block()
.bodyToMono(String.class)
.block();
String response3 = request2
.retrieve()
.bodyToMono(String.class)
.block();
ステータスコードが4xx(クライアントエラー)または5xx(サーバーエラー)の場合にWebClientExceptionをスローするbodyToMonoメソッドに注意してください。 Monosでblockメソッドを使用して、応答とともに送信された実際のデータをサブスクライブして取得しました。
5. WebTestClientの操作
WebTestClientは、WebFluxサーバーエンドポイントをテストするための主要なエントリポイントです。 WebClientと非常によく似たAPIを備えており、主にテストコンテキストの提供に重点を置いて、ほとんどの作業を内部のWebClientインスタンスに委任します。 DefaultWebTestClientクラスは、単一のインターフェイス実装です。
テスト用のクライアントは、実サーバーにバインドすることも、特定のコントローラーまたは機能と連携することもできます。 実行中のサーバーへの実際の要求を使用してエンドツーエンドの統合テストを完了するには、bindToServerメソッドを使用できます。
WebTestClient testClient = WebTestClient
.bindToServer()
.baseUrl("http://localhost:8080")
.build();
特定のRouterFunctionをbindToRouterFunctionメソッドに渡すことで、テストできます。
RouterFunction function = RouterFunctions.route(
RequestPredicates.GET("/resource"),
request -> ServerResponse.ok().build()
);
WebTestClient
.bindToRouterFunction(function)
.build().get().uri("/resource")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
同じ動作は、WebHandlerインスタンスを取得するbindToWebHandlerメソッドでも実現できます。
WebHandler handler = exchange -> Mono.empty();
WebTestClient.bindToWebHandler(handler).build();
bindToApplicationContextメソッドを使用している場合、さらに興味深い状況が発生します。 ApplicationContextを取り、コントローラーBeanと@EnableWebFlux構成のコンテキストを分析します。
ApplicationContextのインスタンスを挿入すると、単純なコードスニペットは次のようになります。
@Autowired
private ApplicationContext context;
WebTestClient testClient = WebTestClient.bindToApplicationContext(context)
.build();
より短いアプローチは、bindToControllerメソッドでテストするコントローラーの配列を提供することです。 Controllerクラスがあり、それを必要なクラスに注入したとすると、次のように書くことができます。
@Autowired
private Controller controller;
WebTestClient testClient = WebTestClient.bindToController(controller).build();
WebTestClientオブジェクトを構築した後、チェーン内の後続のすべての操作は、exchangeメソッド(応答を取得する1つの方法)までのWebClientと同様になります。 (t3)sインターフェースは、expectStatus、expectBody、expectHeaderなどの便利なメソッドを操作します。
WebTestClient
.bindToServer()
.baseUrl("http://localhost:8080")
.build()
.post()
.uri("/resource")
.exchange()
.expectStatus().isCreated()
.expectHeader().valueEquals("Content-Type", "application/json")
.expectBody().isEmpty();
6. 結論
このチュートリアルでは、クライアント側でリクエストを行うための新しい拡張SpringメカニズムであるWebClientクラスについて検討しました。
また、リクエスト処理を最後まで実行することで得られる利点を確認しました。
記事に記載されているすべてのコードスニペットは、our GitHub repositoryにあります。