You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
13 KiB
547 lines
13 KiB
[[webflux-client-builder]] |
|
= Configuration |
|
|
|
The simplest way to create a `WebClient` is through one of the static factory methods: |
|
|
|
* `WebClient.create()` |
|
* `WebClient.create(String baseUrl)` |
|
|
|
You can also use `WebClient.builder()` with further options: |
|
|
|
* `uriBuilderFactory`: Customized `UriBuilderFactory` to use as a base URL. |
|
* `defaultUriVariables`: default values to use when expanding URI templates. |
|
* `defaultHeader`: Headers for every request. |
|
* `defaultCookie`: Cookies for every request. |
|
* `defaultRequest`: `Consumer` to customize every request. |
|
* `filter`: Client filter for every request. |
|
* `exchangeStrategies`: HTTP message reader/writer customizations. |
|
* `clientConnector`: HTTP client library settings. |
|
* `observationRegistry`: the registry to use for enabling xref:integration/observability.adoc#http-client.webclient[Observability support]. |
|
* `observationConvention`: xref:integration/observability.adoc#config[an optional, custom convention to extract metadata] for recorded observations. |
|
|
|
For example: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
WebClient client = WebClient.builder() |
|
.codecs(configurer -> ... ) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val webClient = WebClient.builder() |
|
.codecs { configurer -> ... } |
|
.build() |
|
---- |
|
====== |
|
|
|
Once built, a `WebClient` is immutable. However, you can clone it and build a |
|
modified copy as follows: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
WebClient client1 = WebClient.builder() |
|
.filter(filterA).filter(filterB).build(); |
|
|
|
WebClient client2 = client1.mutate() |
|
.filter(filterC).filter(filterD).build(); |
|
|
|
// client1 has filterA, filterB |
|
|
|
// client2 has filterA, filterB, filterC, filterD |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val client1 = WebClient.builder() |
|
.filter(filterA).filter(filterB).build() |
|
|
|
val client2 = client1.mutate() |
|
.filter(filterC).filter(filterD).build() |
|
|
|
// client1 has filterA, filterB |
|
|
|
// client2 has filterA, filterB, filterC, filterD |
|
---- |
|
====== |
|
|
|
[[webflux-client-builder-maxinmemorysize]] |
|
== MaxInMemorySize |
|
|
|
Codecs have xref:web/webflux/reactive-spring.adoc#webflux-codecs-limits[limits] for buffering data in |
|
memory to avoid application memory issues. By default those are set to 256KB. |
|
If that's not enough you'll get the following error: |
|
|
|
---- |
|
org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer |
|
---- |
|
|
|
To change the limit for default codecs, use the following: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
WebClient webClient = WebClient.builder() |
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val webClient = WebClient.builder() |
|
.codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) } |
|
.build() |
|
---- |
|
====== |
|
|
|
|
|
|
|
[[webflux-client-builder-reactor]] |
|
== Reactor Netty |
|
|
|
To customize Reactor Netty settings, provide a pre-configured `HttpClient`: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...); |
|
|
|
WebClient webClient = WebClient.builder() |
|
.clientConnector(new ReactorClientHttpConnector(httpClient)) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val httpClient = HttpClient.create().secure { ... } |
|
|
|
val webClient = WebClient.builder() |
|
.clientConnector(ReactorClientHttpConnector(httpClient)) |
|
.build() |
|
---- |
|
====== |
|
|
|
|
|
[[webflux-client-builder-reactor-resources]] |
|
=== Resources |
|
|
|
By default, `HttpClient` participates in the global Reactor Netty resources held in |
|
`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 |
|
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 |
|
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 |
|
`ReactorResourceFactory` with `globalResources=true` (the default) to ensure that the Reactor |
|
Netty global resources are shut down when the Spring `ApplicationContext` is closed, |
|
as the following example shows: |
|
|
|
-- |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@Bean |
|
public ReactorResourceFactory reactorResourceFactory() { |
|
return new ReactorResourceFactory(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@Bean |
|
fun reactorResourceFactory() = ReactorResourceFactory() |
|
---- |
|
====== |
|
-- |
|
|
|
You can also choose not to participate in the global Reactor Netty resources. However, |
|
in this mode, the burden is on you to ensure that all Reactor Netty client and server |
|
instances use shared resources, as the following example shows: |
|
|
|
-- |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@Bean |
|
public ReactorResourceFactory resourceFactory() { |
|
ReactorResourceFactory factory = new ReactorResourceFactory(); |
|
factory.setUseGlobalResources(false); // <1> |
|
return factory; |
|
} |
|
|
|
@Bean |
|
public WebClient webClient() { |
|
|
|
Function<HttpClient, HttpClient> mapper = client -> { |
|
// Further customizations... |
|
}; |
|
|
|
ClientHttpConnector connector = |
|
new ReactorClientHttpConnector(resourceFactory(), mapper); // <2> |
|
|
|
return WebClient.builder().clientConnector(connector).build(); // <3> |
|
} |
|
---- |
|
<1> Create resources independent of global ones. |
|
<2> Use the `ReactorClientHttpConnector` constructor with resource factory. |
|
<3> Plug the connector into the `WebClient.Builder`. |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@Bean |
|
fun resourceFactory() = ReactorResourceFactory().apply { |
|
isUseGlobalResources = false // <1> |
|
} |
|
|
|
@Bean |
|
fun webClient(): WebClient { |
|
|
|
val mapper: (HttpClient) -> HttpClient = { |
|
// Further customizations... |
|
} |
|
|
|
val connector = ReactorClientHttpConnector(resourceFactory(), mapper) // <2> |
|
|
|
return WebClient.builder().clientConnector(connector).build() // <3> |
|
} |
|
---- |
|
<1> Create resources independent of global ones. |
|
<2> Use the `ReactorClientHttpConnector` constructor with resource factory. |
|
<3> Plug the connector into the `WebClient.Builder`. |
|
====== |
|
-- |
|
|
|
|
|
[[webflux-client-builder-reactor-timeout]] |
|
=== Timeouts |
|
|
|
To configure a connection timeout: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
import io.netty.channel.ChannelOption; |
|
|
|
HttpClient httpClient = HttpClient.create() |
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000); |
|
|
|
WebClient webClient = WebClient.builder() |
|
.clientConnector(new ReactorClientHttpConnector(httpClient)) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
import io.netty.channel.ChannelOption |
|
|
|
val httpClient = HttpClient.create() |
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000); |
|
|
|
val webClient = WebClient.builder() |
|
.clientConnector(ReactorClientHttpConnector(httpClient)) |
|
.build(); |
|
---- |
|
====== |
|
|
|
To configure a read or write timeout: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
import io.netty.handler.timeout.ReadTimeoutHandler; |
|
import io.netty.handler.timeout.WriteTimeoutHandler; |
|
|
|
HttpClient httpClient = HttpClient.create() |
|
.doOnConnected(conn -> conn |
|
.addHandlerLast(new ReadTimeoutHandler(10)) |
|
.addHandlerLast(new WriteTimeoutHandler(10))); |
|
|
|
// Create WebClient... |
|
|
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
import io.netty.handler.timeout.ReadTimeoutHandler |
|
import io.netty.handler.timeout.WriteTimeoutHandler |
|
|
|
val httpClient = HttpClient.create() |
|
.doOnConnected { conn -> conn |
|
.addHandlerLast(ReadTimeoutHandler(10)) |
|
.addHandlerLast(WriteTimeoutHandler(10)) |
|
} |
|
|
|
// Create WebClient... |
|
---- |
|
====== |
|
|
|
To configure a response timeout for all requests: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
HttpClient httpClient = HttpClient.create() |
|
.responseTimeout(Duration.ofSeconds(2)); |
|
|
|
// Create WebClient... |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val httpClient = HttpClient.create() |
|
.responseTimeout(Duration.ofSeconds(2)); |
|
|
|
// Create WebClient... |
|
---- |
|
====== |
|
|
|
To configure a response timeout for a specific request: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
WebClient.create().get() |
|
.uri("https://example.org/path") |
|
.httpRequest(httpRequest -> { |
|
HttpClientRequest reactorRequest = httpRequest.getNativeRequest(); |
|
reactorRequest.responseTimeout(Duration.ofSeconds(2)); |
|
}) |
|
.retrieve() |
|
.bodyToMono(String.class); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
WebClient.create().get() |
|
.uri("https://example.org/path") |
|
.httpRequest { httpRequest: ClientHttpRequest -> |
|
val reactorRequest = httpRequest.getNativeRequest<HttpClientRequest>() |
|
reactorRequest.responseTimeout(Duration.ofSeconds(2)) |
|
} |
|
.retrieve() |
|
.bodyToMono(String::class.java) |
|
---- |
|
====== |
|
|
|
|
|
|
|
[[webflux-client-builder-jdk-httpclient]] |
|
== JDK HttpClient |
|
|
|
The following example shows how to customize the JDK `HttpClient`: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
HttpClient httpClient = HttpClient.newBuilder() |
|
.followRedirects(Redirect.NORMAL) |
|
.connectTimeout(Duration.ofSeconds(20)) |
|
.build(); |
|
|
|
ClientHttpConnector connector = |
|
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory()); |
|
|
|
WebClient webClient = WebClient.builder().clientConnector(connector).build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val httpClient = HttpClient.newBuilder() |
|
.followRedirects(Redirect.NORMAL) |
|
.connectTimeout(Duration.ofSeconds(20)) |
|
.build() |
|
|
|
val connector = JdkClientHttpConnector(httpClient, DefaultDataBufferFactory()) |
|
|
|
val webClient = WebClient.builder().clientConnector(connector).build() |
|
---- |
|
====== |
|
|
|
|
|
|
|
[[webflux-client-builder-jetty]] |
|
== Jetty |
|
|
|
The following example shows how to customize Jetty `HttpClient` settings: |
|
|
|
-- |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
HttpClient httpClient = new HttpClient(); |
|
httpClient.setCookieStore(...); |
|
|
|
WebClient webClient = WebClient.builder() |
|
.clientConnector(new JettyClientHttpConnector(httpClient)) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val httpClient = HttpClient() |
|
httpClient.cookieStore = ... |
|
|
|
val webClient = WebClient.builder() |
|
.clientConnector(JettyClientHttpConnector(httpClient)) |
|
.build(); |
|
---- |
|
====== |
|
-- |
|
|
|
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`), |
|
which remain active until the process exits or `stop()` is called. |
|
|
|
You can share resources between multiple instances of the Jetty client (and server) and |
|
ensure that the resources are shut down when the Spring `ApplicationContext` is closed by |
|
declaring a Spring-managed bean of type `JettyResourceFactory`, as the following example |
|
shows: |
|
|
|
-- |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@Bean |
|
public JettyResourceFactory resourceFactory() { |
|
return new JettyResourceFactory(); |
|
} |
|
|
|
@Bean |
|
public WebClient webClient() { |
|
|
|
HttpClient httpClient = new HttpClient(); |
|
// Further customizations... |
|
|
|
ClientHttpConnector connector = |
|
new JettyClientHttpConnector(httpClient, resourceFactory()); <1> |
|
|
|
return WebClient.builder().clientConnector(connector).build(); <2> |
|
} |
|
---- |
|
<1> Use the `JettyClientHttpConnector` constructor with resource factory. |
|
<2> Plug the connector into the `WebClient.Builder`. |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@Bean |
|
fun resourceFactory() = JettyResourceFactory() |
|
|
|
@Bean |
|
fun webClient(): WebClient { |
|
|
|
val httpClient = HttpClient() |
|
// Further customizations... |
|
|
|
val connector = JettyClientHttpConnector(httpClient, resourceFactory()) // <1> |
|
|
|
return WebClient.builder().clientConnector(connector).build() // <2> |
|
} |
|
---- |
|
<1> Use the `JettyClientHttpConnector` constructor with resource factory. |
|
<2> Plug the connector into the `WebClient.Builder`. |
|
====== |
|
-- |
|
|
|
|
|
|
|
[[webflux-client-builder-http-components]] |
|
== HttpComponents |
|
|
|
The following example shows how to customize Apache HttpComponents `HttpClient` settings: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom(); |
|
clientBuilder.setDefaultRequestConfig(...); |
|
CloseableHttpAsyncClient client = clientBuilder.build(); |
|
|
|
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client); |
|
|
|
WebClient webClient = WebClient.builder().clientConnector(connector).build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
val client = HttpAsyncClients.custom().apply { |
|
setDefaultRequestConfig(...) |
|
}.build() |
|
val connector = HttpComponentsClientHttpConnector(client) |
|
val webClient = WebClient.builder().clientConnector(connector).build() |
|
---- |
|
====== |
|
|
|
|
|
|