@ -7,14 +7,13 @@ using a functional-style API that exposes Reactor `Flux` and `Mono` types, see
<<web-reactive.adoc#webflux-codecs,codecs>> that WebFlux server applications use to work
<<web-reactive.adoc#webflux-codecs,codecs>> that WebFlux server applications use to work
with request and response content.
with request and response content.
Internally `WebClient` delegates to an HTTP client library. By default it uses
Internally `WebClient` delegates to an HTTP client library. By default, it uses
https://github.com/reactor/reactor-netty[Reactor Netty], there is built-in support for
https://github.com/reactor/reactor-netty[Reactor Netty], there is built-in support for
the Jetty https://github.com/jetty-project/jetty-reactive-httpclient[reactive HtpClient],
the Jetty https://github.com/jetty-project/jetty-reactive-httpclient[reactive HtpClient],
and others can be plugged in through a `ClientHttpConnector`.
and others can be plugged in through a `ClientHttpConnector`.
[[webflux-client-builder]]
[[webflux-client-builder]]
== Configuration
== Configuration
@ -23,22 +22,23 @@ The simplest way to create a `WebClient` is through one of the static factory me
* `WebClient.create()`
* `WebClient.create()`
* `WebClient.create(String baseUrl)`
* `WebClient.create(String baseUrl)`
The above uses Reactor Netty `HttpClient` from "io.projectreactor.netty:reactor-netty"
The preceding methods use Reactor Netty `HttpClient` from `io.projectreactor.netty:reactor-netty`
with default settings and participates in global resources such for event loop threads and
with default settings and participates in global resources for event loop threads and
a connection pool, s ee <<webflux-client-builder-reactor, Reactor Netty configuration>>.
a connection pool. S ee <<webflux-client-builder-reactor, Reactor Netty configuration>>.
The `WebClient.Builder` can be used for access to further options:
You can use the `WebClient.Builder` for access to further options:
* `uriBuilderFactory` -- c ustomized `UriBuilderFactory` to use as a base URL.
* `uriBuilderFactory`: C ustomized `UriBuilderFactory` to use as a base URL.
* `defaultHeader` -- h eaders for every request.
* `defaultHeader`: H eaders for every request.
* `defaultCookie)` -- c ookies for every request.
* `defaultCookie)`: C ookies for every request.
* `defaultRequest` -- `Consumer` to customize every request.
* `defaultRequest`: `Consumer` to customize every request.
* `filter` -- c lient filter for every request.
* `filter`: C lient filter for every request.
* `exchangeStrategies` -- HTTP message reader/writer customizations.
* `exchangeStrategies`: HTTP message reader/writer customizations.
* `clientConnector` -- HTTP client library settings.
* `clientConnector`: HTTP client library settings.
For example, to configure <<web-reactive.adoc#webflux-codecs,HTTP codecs>>:
The following example configures <<web-reactive.adoc#webflux-codecs,HTTP codecs>>:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -52,10 +52,12 @@ For example, to configure <<web-reactive.adoc#webflux-codecs,HTTP codecs>>:
.exchangeStrategies(strategies)
.exchangeStrategies(strategies)
.build();
.build();
----
----
====
Once built a `WebClient` instance is immutable. However, you can clone it, and build a
Once built, a `WebClient` instance is immutable. However, you can clone it and build a
modified copy without affecting the original instance:
modified copy without affecting the original instance, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -69,14 +71,16 @@ modified copy without affecting the original instance:
// client2 has filterA, filterB, filterC, filterD
// client2 has filterA, filterB, filterC, filterD
----
----
====
[[webflux-client-builder-reactor]]
[[webflux-client-builder-reactor]]
=== Reactor Netty
=== Reactor Netty
To customize Reactor Netty settings:
You can customize Reactor Netty settings:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -85,18 +89,21 @@ To customize Reactor Netty settings:
WebClient webClient = WebClient.builder().clientConnector(connector).build();
WebClient webClient = WebClient.builder().clientConnector(connector).build();
----
----
====
By default `HttpClient` participates in the global Reactor Netty resources held in
By default, `HttpClient` participates in the global Reactor Netty resources held in
`reactor.netty.http.HttpResources`, including event loop threads and a connection pool.
`reactor.netty.http.HttpResources`, including event loop threads and a connection pool.
This is the recommended mode since fixed, shared resources are preferred for event loop
This is the recommended mode, since fixed, shared resources are preferred for event loop
concurrency. In this mode global resources remain active until the process exits.
concurrency. In this mode global resources remain active until the process exits.
If the server is timed with the process, there is typically no need for an explicit
If the server is timed with the process, there is typically no need for an explicit
shutdown. However if the server can start or stop in-process, e.g. Spring MVC
shutdown. However, if the server can start or stop in-process (for example, a Spring MVC
application deployed as a WAR, you can declare a Spring-managed bean of type
application deployed as a WAR), you can declare a Spring-managed bean of type
`ReactorResourceFactory` with `useGlobalResources=true` (the default) to ensure the Reactor
`ReactorResourceFactory` with `globalResources=true` (the default) to ensure that the Reactor
Netty global resources are shut down when the Spring `ApplicationContext` is closed:
Netty global resources are shut down when the Spring `ApplicationContext` is closed,
as the following example shows:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -105,11 +112,13 @@ Netty global resources are shut down when the Spring `ApplicationContext` is clo
return new ReactorResourceFactory();
return new ReactorResourceFactory();
}
}
----
----
====
You may also choose not to participate in the global Reactor Netty resources. However keep
You can also choose not to participate in the global Reactor Netty resources. However,
in mind in this mode the burden is on you to ensure all Reactor Netty client and server
in this mode, the burden is on you to ensure that all Reactor Netty client and server
instances use shared resources:
instances use shared resources, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -134,15 +143,18 @@ instances use shared resources:
}
}
----
----
<1> Create resources independent of global ones.
<1> Create resources independent of global ones.
<2> Use `ReactorClientHttpConnector` constructor with resource factory.
<2> Use the `ReactorClientHttpConnector` constructor with resource factory.
<3> Plug the connector into the `WebClient.Builder`.
<3> Plug the connector into the `WebClient.Builder`.
====
[[webflux-client-builder-jetty]]
[[webflux-client-builder-jetty]]
=== Jetty
=== Jetty
To customize Jetty `HttpClient` settings:
The following example shows how t o customize Jetty `HttpClient` settings:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -152,14 +164,16 @@ To customize Jetty `HttpClient` settings:
WebClient webClient = WebClient.builder().clientConnector(connector).build();
WebClient webClient = WebClient.builder().clientConnector(connector).build();
----
----
====
By default `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`)
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`),
which remain active until the process exits or `stop()` is called.
which remain active until the process exits or `stop()` is called.
You can share resources between multiple intances of Jetty client (and server) and ensure the
You can share resources between multiple ins tances of the Jetty client (and server) and ensure that the
resources are shut down when the Spring `ApplicationContext` is closed by declaring a
resources are shut down when the Spring `ApplicationContext` is closed by declaring a
Spring-managed bean of type `JettyResourceFactory`:
Spring-managed bean of type `JettyResourceFactory`, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -183,16 +197,19 @@ Spring-managed bean of type `JettyResourceFactory`:
----
----
<1> Create shared resources.
<1> Create shared resources.
<2> Use `JettyClientHttpConnector` constructor with resource factory.
<2> Use the `JettyClientHttpConnector` constructor with resource factory.
<3> Plug the connector into the `WebClient.Builder`.
<3> Plug the connector into the `WebClient.Builder`.
====
[[webflux-client-retrieve]]
[[webflux-client-retrieve]]
== Retrieve
== Using the `retrieve` Method
The `retrieve()` method is the easiest way to get a response body and decode it:
The `retrieve()` method is the easiest way to get a response body and decode it.
The following example shows how to do so:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -203,9 +220,11 @@ The `retrieve()` method is the easiest way to get a response body and decode it:
.retrieve()
.retrieve()
.bodyToMono(Person.class);
.bodyToMono(Person.class);
----
----
====
You can also get a stream of objects decoded from the response:
You can also get a stream of objects decoded from the response, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -214,12 +233,15 @@ You can also get a stream of objects decoded from the response:
.retrieve()
.retrieve()
.bodyToFlux(Quote.class);
.bodyToFlux(Quote.class);
----
----
====
By default, responses with 4xx or 5xx status codes result in an
By default, responses with 4xx or 5xx status codes result in an
`WebClientResponseException` or one of its HTTP status specific sub-classes such as
`WebClientResponseException` or one of its HTTP status specific sub-classes, such as
`WebClientResponseException.BadRequest`, `WebClientResponseException.NotFound`, and others.
`WebClientResponseException.BadRequest`, `WebClientResponseException.NotFound`, and others.
You can also use the `onStatus` method to customize the resulting exception:
You can also use the `onStatus` method to customize the resulting exception,
as the following example shows:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -230,16 +252,17 @@ You can also use the `onStatus` method to customize the resulting exception:
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
.bodyToMono(Person.class);
----
----
====
[[webflux-client-exchange]]
[[webflux-client-exchange]]
== Exchange
== Using the `exchange` Method
The `exchange()` method provides more control. The below example is equivalent
The `exchange()` method provides more control than the `retrieve` method. The following example is equivalent
to `retrieve()` but also provides access to the `ClientResponse`:
to `retrieve()` but also provides access to the `ClientResponse`:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -248,9 +271,11 @@ to `retrieve()` but also provides access to the `ClientResponse`:
.exchange()
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
.flatMap(response -> response.bodyToMono(Person.class));
----
----
====
At this level you can also create a full `ResponseEntity`:
At this level, you can also create a full `ResponseEntity`:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -259,27 +284,25 @@ At this level you can also create a full `ResponseEntity`:
.exchange()
.exchange()
.flatMap(response -> response.toEntity(Person.class));
.flatMap(response -> response.toEntity(Person.class));
----
----
====
Note that unlike `retrieve()`, with `exchange()` there are no automatic error signals for
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.
4xx and 5xx responses. You have to check the status code and decide how to proceed.
[CAUTION]
CAUTION: When you use `exchange()`, you must always use any of the `body` or `toEntity` methods of
====
When using `exchange()` you must always use any of the body or toEntity methods of
`ClientResponse` to ensure resources are released and to avoid potential issues with HTTP
`ClientResponse` to ensure resources are released and to avoid potential issues with HTTP
connection pooling. You can use `bodyToMono(Void.class)` if no response content is
connection pooling. You can use `bodyToMono(Void.class)` if no response content is
expected. However keep in mind that if the response does have content, the connection
expected. However, if the response does have content, the connection
will be closed and will not be placed back in the pool.
is closed and is not placed back in the pool.
====
[[webflux-client-body]]
[[webflux-client-body]]
== Request b ody
== Request B ody
The request body can be encoded from an Object:
The request body can be encoded from an ` Object`, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -292,9 +315,11 @@ The request body can be encoded from an Object:
.retrieve()
.retrieve()
.bodyToMono(Void.class);
.bodyToMono(Void.class);
----
----
====
You can also have a stream of objects encoded:
You can also have a stream of objects be encoded, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -307,9 +332,12 @@ You can also have a stream of objects encoded:
.retrieve()
.retrieve()
.bodyToMono(Void.class);
.bodyToMono(Void.class);
----
----
====
Or if you have the actual value, use the `syncBody` shortcut method:
Alternatively, if you have the actual value, you can use the `syncBody` shortcut method,
as the following example shows:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -322,16 +350,18 @@ Or if you have the actual value, use the `syncBody` shortcut method:
.retrieve()
.retrieve()
.bodyToMono(Void.class);
.bodyToMono(Void.class);
----
----
====
[[webflux-client-body-form]]
[[webflux-client-body-form]]
=== Form d ata
=== Form D ata
To send form data, provide a `MultiValueMap<String, String>` as the body. Note that the
To send form data, you can provide a `MultiValueMap<String, String>` as the body. Note that the
content is automatically set to `" application/x-www-form-urlencoded" ` by the
content is automatically set to `application/x-www-form-urlencoded` by the
`FormHttpMessageWriter`:
`FormHttpMessageWriter`. The following example shows how to use `MultiValueMap<String, String>` :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -343,9 +373,11 @@ content is automatically set to `"application/x-www-form-urlencoded"` by the
.retrieve()
.retrieve()
.bodyToMono(Void.class);
.bodyToMono(Void.class);
----
----
====
You can also supply form data in-line via `BodyInserters` :
You can also supply form data in-line by using `BodyInserters`, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -357,16 +389,17 @@ You can also supply form data in-line via `BodyInserters`:
.retrieve()
.retrieve()
.bodyToMono(Void.class);
.bodyToMono(Void.class);
----
----
====
[[webflux-client-body-multipart]]
[[webflux-client-body-multipart]]
=== Multipart d ata
=== Multipart D ata
To send multipart data, you need to provide a `MultiValueMap<String, ?>` whose values are
To send multipart data, you need to provide a `MultiValueMap<String, ?>` whose values are
either Objects representing part content, or `HttpEntity` representing the content and
either ` Object` in stances that represent part content or `HttpEntity` instances that represent the content and
headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a
headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a
multipart request:
multipart request. The following example shows how to create a `MultiValueMap<String, ?>` :
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
@ -379,15 +412,16 @@ multipart request:
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
----
----
In most cases you do not have to specify the `Content-Type` for each part. The content
In most cases, you do not have to specify the `Content-Type` for each part. The content
type is determined automatically based on the `HttpMessageWriter` chosen to serialize it,
type is determined automatically based on the `HttpMessageWriter` chosen to serialize it
or in the case of a `Resource` based on the file extension. If necessary you can
or, in the case of a `Resource`, based on the file extension. If necessary, you can
explicitly provide the `MediaType` to use for each part through one f o the overloaded
explicitly provide the `MediaType` to use for each part through one of the overloaded
builder `part` methods.
builder `part` methods.
Once a `MultiValueMap` is prepared, the easiest way to pass it to the the `WebClient` is
Once a `MultiValueMap` is prepared, the easiest way to pass it to the the `WebClient` is
through the `syncBody` method:
through the `syncBody` method, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -399,15 +433,17 @@ through the `syncBody` method:
.retrieve()
.retrieve()
.bodyToMono(Void.class);
.bodyToMono(Void.class);
----
----
====
If the `MultiValueMap` contains at least one non-String value, which could also be
If the `MultiValueMap` contains at least one non-` String` value, which could also
represent regular form data (i.e. "application/x-www-form-urlencoded"), you don't have to
represent regular form data (that is, `application/x-www-form-urlencoded`), you need not
set the `Content-Type` to "multipart/form-data" . This is always the case when using
set the `Content-Type` to `multipart/form-data` . This is always the case when using
`MultipartBodyBuilder` which ensures an `HttpEntity` wrapper.
`MultipartBodyBuilder`, which ensures an `HttpEntity` wrapper.
As an alternative to `MultipartBodyBuilder`, you can also provide multipart content,
As an alternative to `MultipartBodyBuilder`, you can also provide multipart content,
inline-style, through the built-in `BodyInserters`. For example :
inline-style, through the built-in `BodyInserters`, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -419,7 +455,7 @@ inline-style, through the built-in `BodyInserters`. For example:
.retrieve()
.retrieve()
.bodyToMono(Void.class);
.bodyToMono(Void.class);
----
----
====
@ -427,8 +463,9 @@ inline-style, through the built-in `BodyInserters`. For example:
== Client Filters
== Client Filters
You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder`
You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder`
in order to intercept and/or modify requests:
in order to intercept and modify requests, as the following example show s:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -443,10 +480,12 @@ WebClient client = WebClient.builder()
})
})
.build();
.build();
----
----
====
This can be used for cross-cutting concerns such as authentication. The example below uses
This can be used for cross-cutting concerns, such as authentication. The following example uses
a filter for basic authentication through a static factory method:
a filter for basic authentication through a static factory method:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -457,11 +496,13 @@ WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "password"))
.filter(basicAuthentication("user", "password"))
.build();
.build();
----
----
====
Filters apply globally to every request. To change how a filter's behavior for a specific
Filters apply globally to every request. To change a filter's behavior for a specific
request, you can add request attributes to the `ClientRequest` that can then be accessed
request, you can add request attributes to the `ClientRequest` that can then be accessed
by all filters in the chain:
by all filters in the chain, as the following example shows :
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -479,11 +520,13 @@ client.get().uri("http://example.org/")
}
}
----
----
====
You can also replicate an existing `WebClient`, and insert new filters or remove already
You can also replicate an existing `WebClient`, insert new filters, or remove already
registered filters. In the example below, a basic authentication filter is inserted at
registered filters. The following example, inserts a basic authentication filter at
index 0:
index 0:
====
[source,java,intent=0]
[source,java,intent=0]
[subs="verbatim,quotes"]
[subs="verbatim,quotes"]
----
----
@ -496,17 +539,17 @@ WebClient client = webClient.mutate()
})
})
.build();
.build();
----
----
====
[[webflux-client-testing]]
[[webflux-client-testing]]
== Testing
== Testing
To test code that uses the `WebClient`, you can use a mock web server such as the
To test code that uses the `WebClient`, you can use a mock web server, such as the
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see example
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see an example
use, check
of its use, check
https://github.com/spring-projects/spring-framework/blob/master/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[WebClientIntegrationTests]
https://github.com/spring-projects/spring-framework/blob/master/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[` WebClientIntegrationTests` ]
in the Spring Framework tests, or the
in the Spring Framework tests or the
https://github.com/square/okhttp/tree/master/samples/static-server[static-server]
https://github.com/square/okhttp/tree/master/samples/static-server[` static-server` ]
sample in the OkHttp repository.
sample in the OkHttp repository.