The spring-webflux
module includes a non-blocking, reactive client for HTTP requests
with Reactive Streams back pressure. It shares
HTTP codecs and other infrastructure with the
server functional web framework.
WebClient
provides a higher level API over HTTP client libraries. By default
it uses 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).
Tip
|
By comparison to the
RestTemplate, the |
The retrieve()
method is the easiest way to get a response body and decode it:
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:
Flux<Quote> result = client.get()
.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:
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);
The exchange()
method provides more control. The below example is equivalent
to retrieve()
but also provides access to the ClientResponse
:
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
:
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 |
The request body can be encoded from an 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);
You can also have a stream of objects encoded:
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:
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);
To send form data, provide a MultiValueMap<String, String>
as the body. Note that the
content is automatically set to "application/x-www-form-urlencoded"
by the
FormHttpMessageWriter
:
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(formData)
.retrieve()
.bodyToMono(Void.class);
You can also supply form data in-line via 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);
To send multipart data, provide a MultiValueMap<String, ?>
where values are either an
Object representing the part body, or an HttpEntity
representing the part body and
headers. MultipartBodyBuilder
can be used to build the parts:
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);
Note that the content type for each part is automatically set based on the extension of the file being written or the type of Object. If you prefer you can also be more explicit and specify the content type for each part.
You can also supply multipart data in-line via 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);
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:
SslContext sslContext = ...
ClientHttpConnector connector = new ReactorClientHttpConnector(
builder -> builder.sslContext(sslContext));
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.build();
To customize the HTTP codecs used for encoding and decoding HTTP messages:
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
// ...
})
.build();
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies)
.build();
The builder can be used to insert Filters.
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:
WebClient modifiedClient = client.mutate()
// user builder methods...
.build();
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
provides a filter for basic authentication:
// 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:
WebClient filteredClient = client.mutate()
.filter(basicAuthentication("user", "pwd")
.build();