https://docs.spring.io/spring/docs/5.0.3.RELEASE/spring-framework-reference/web-reactive.html#webflux-client
2. WebClient
spring-webflux
モジュールには、Reactive Streamsのバックプレッシャを用いるHTTPリクエスト用のノンブロッキング・リアクティブなクライアントが含まれます。
このクライアントはHTTP codecsおよびサーバのfunctional web frameworkの基礎部分は共通です。
WebClient
はHTTPクライアントライブラリを下側に置く高レベルのAPIです。デフォルトではReactor Nettyを使いますが、別のClientHttpConnector
を使用してプラグイン可能です。WebClient
APIは出力にFlux
かMono
を返し、入力にReactive StreamsのPublisher
を受けます。(Reactive Libraries参照)
RestTemplateと比較すると、WebClient
はより関数型でJava 8のラムダを取り入れたfluent APIです。同期・非同期の両方を備え、ストリーミングを含むので、ノンブロッキングI/Oで有用です。
2.1. Retrieve
retrieve()
メソッドで簡単にレスポンスボディを取得してデコードできます。
WebClient client = WebClient.create("http://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
また、レスポンスからデコードするオブジェクトのストリームを得られます。
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
デフォルトでは、4xxか5xxのステータスコードのレスポンスはWebClientResponseException
のエラーになりますが、これはカスタマイズ可能です。
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
2.2. Exchange
exchange()
ではより細かい制御ができます。以下の例はretrieve()
と同等ですが、ClientResponse
にアクセスしています。
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
この段階で、完全なResponseEntity
を生成することもできます。
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(Person.class));
retrieve()
と異なり、exchange()
では、4xxと5xxレスポンスにおける自動的なエラー通知はありません。自前でステータスコードをチェックして続行するかを決めるコードを書く必要があります。
exchange()
を使う場合、ClientResponse
のtoEntityかbodyメソッドのいずれかを必ず使用してください。これは、リソースを開放してHTTPコネクションプーリングに潜在的な問題を抱えるのを回避するためです。もしレスポンスの中身が無い場合はbodyToMono(Void.class)
を使います。ただし、レスポンスの中身がある場合、コネクションは閉じてプールには戻らない点に注意してください。
2.3. Request body
リクエストボディはObjectからエンコードします。
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
エンコードされたオブジェクトのストリームも使えます。
Flux<Person> personFlux = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(personFlux, Person.class)
.retrieve()
.bodyToMono(Void.class);
値そのままを使う場合、syncBody
というショートカットのメソッドを使います。
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);
2.3.1. Form data
フォームデータの送信にはボディにMultiValueMap<String, String>
を使用します。なお、MultiValueMap<String, String>
によって自動的に"application/x-www-form-urlencoded"
をセットします。
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(formData)
.retrieve()
.bodyToMono(Void.class);
BodyInserters
でインラインにフォームデータを記述できます。
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
2.3.2. Multipart data
マルチパートデータの送信にはMultiValueMap<String, ?>
を使用し、その値には、パートのボディを表すObjectか、パートのボディとヘッダーを表すHttpEntity
、のどちらかを使います。パートを作るにはMultipartBodyBuilder
を使います。
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(parts)
.retrieve()
.bodyToMono(Void.class);
なお、各パートのコンテンツタイプは書き込むファイルの拡張子かObjectの型に基づいて自動設定します。また、各パートごとにコンテンツタイプを明示的に指定することも可能です。
BodyInserters
でインラインにマルチパートデータを記述するやり方もあります。
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);
2.4. Builder options
WebClient
の生成は簡単で、staticファクトリメソッドcreate()
と、すべてのリクエストのベースとなるURLを与えるcreate(String)
で行います。WebClient.builder()
にはより細かいオプションがあります。
基底のHTTPクライアントをカスタマイズするには以下のようにします。
SslContext sslContext = ...
ClientHttpConnector connector = new ReactorClientHttpConnector(
builder -> builder.sslContext(sslContext));
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.build();
HTTPメッセージのエンコード・デコードで使われるHTTP codecsをカスタマイズするには以下のようにします。
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
})
.build();
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies)
.build();
ビルダーはFiltersを追加するのに使用します。
URIの構築やデフォルトヘッダー(とクッキー)などその他のオプションについてはIDEでWebClient.Builder
を確認してみてください。
WebClient
の作成後は、新しくWebClient
を生成するための新しいbuilderを常に取得可能です。元のインスタンスをベースにしますが、現在のインスタンスには何も影響を与えません。
WebClient modifiedClient = client.mutate()
.build();
2.5. Filters
WebClient
supports interception style request filtering:
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
ExchangeFilterFunctions
はベーシック認証用のフィルターです。
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "pwd"))
.build();
また、オリジナルのインスタンスに影響を与えることなくWebClient
インスタンスを変更できます*1。