spring-framework/src/docs/asciidoc/web/webflux-webclient.adoc

263 lines
7.1 KiB
Plaintext
Raw Normal View History

[[webflux-client]]
= WebClient
The `spring-webflux` module includes a non-blocking, reactive client for HTTP requests
with Reactive Streams back pressure. It shares
<<web-reactive.adoc#webflux-codecs,HTTP codecs>> and other infrastructure with the
server <<web-reactive.adoc#webflux-fn,functional web framework>>.
`WebClient` provides a higher level API over HTTP client libraries. By default
it uses https://github.com/reactor/reactor-netty[Reactor Netty] but that is pluggable
with a different `ClientHttpConnector`. The `WebClient` API returns Reactor `Flux` or
`Mono` for output and accepts Reactive Streams `Publisher` as input (see
<<web-reactive.adoc#webflux-reactive-libraries>>).
[TIP]
====
By comparison to the
<<integration.adoc#rest-resttemplate,RestTemplate>>, the `WebClient` offers a more
functional and fluent API that taking full advantage of Java 8 lambdas. It supports both
sync and async scenarios, including streaming, and brings the efficiency of
non-blocking I/O.
====
[[webflux-client-retrieve]]
== Retrieve
The `retrieve()` method is the easiest way to get a response body and decode it:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient client = WebClient.create("http://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
----
You can also get a stream of objects decoded from the response:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Flux<Quote> result = client.get()
2017-10-11 04:18:09 +08:00
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
----
By default, responses with 4xx or 5xx status codes result in an error of type
`WebClientResponseException` but you can customize that:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
2017-10-11 04:18:09 +08:00
.bodyToMono(Person.class);
----
[[webflux-client-exchange]]
== Exchange
The `exchange()` method provides more control. The below example is equivalent
to `retrieve()` but also provides access to the `ClientResponse`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
----
At this level you can also create a full `ResponseEntity`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(Person.class));
----
Note that unlike `retrieve()`, with `exchange()` there are no automatic error signals for
4xx and 5xx responses. You have to check the status code and decide how to proceed.
[CAUTION]
====
When using `exchange()` you must always use any of the body or entity methods of
`ClientResponse` to ensure resources are released and to avoid potential issues with HTTP
connection pooling. If not interested in the response body use `bodyToMono(Void.class)`
to complete.
====
[[webflux-client-body]]
== Request body
The request body can be encoded from an Object:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
----
You can also have a stream of objects encoded:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
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);
----
Or if you have the actual value, use the `syncBody` shortcut method:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);
----
[[webflux-client-builder]]
== Builder options
A simple way to create `WebClient` is through the static factory methods `create()` and
`create(String)` with a base URL for all requests. You can also use `WebClient.builder()`
for access to more options.
To customize the underlying HTTP client:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
SslContext sslContext = ...
ClientHttpConnector connector = new ReactorClientHttpConnector(
builder -> builder.sslContext(sslContext));
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.build();
----
To customize the <<web-reactive.adoc#webflux-codecs,HTTP codecs>> used for encoding and
decoding HTTP messages:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
// ...
})
.build();
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies)
.build();
----
The builder can be used to insert <<webflux-client-filter>>.
Explore the `WebClient.Builder` in your IDE for other options related to URI building,
default headers (and cookies), and more.
After the `WebClient` is built, you can always obtain a new builder from it, in order to
build a new `WebClient`, based on, but without affecting the current instance:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient modifiedClient = client.mutate()
// user builder methods...
.build();
----
[[webflux-client-filter]]
== Filters
`WebClient` supports interception style request filtering:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
----
`ExchangeFilterFunctions` provides a filter for basic authentication:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "pwd"))
.build();
----
You can also mutate an existing `WebClient` instance without affecting the original:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
WebClient filteredClient = client.mutate()
.filter(basicAuthentication("user", "pwd")
.build();
----