Migrate to Asciidoctor Tabs
This commit is contained in:
@@ -5,8 +5,11 @@ You can add attributes to a request. This is convenient if you want to pass info
|
||||
through the filter chain and influence the behavior of filters for a given request.
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.filter((request, next) -> {
|
||||
@@ -22,8 +25,10 @@ For example:
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.builder()
|
||||
.filter { request, _ ->
|
||||
@@ -37,6 +42,7 @@ For example:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
Note that you can configure a `defaultRequest` callback globally at the
|
||||
`WebClient.Builder` level which lets you insert attributes into all requests,
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
The request body can be encoded from any asynchronous type handled by `ReactiveAdapterRegistry`,
|
||||
like `Mono` or Kotlin Coroutines `Deferred` as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> personMono = ... ;
|
||||
|
||||
@@ -16,8 +19,10 @@ like `Mono` or Kotlin Coroutines `Deferred` as the following example shows:
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val personDeferred: Deferred<Person> = ...
|
||||
|
||||
@@ -28,11 +33,15 @@ like `Mono` or Kotlin Coroutines `Deferred` as the following example shows:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
You can also have a stream of objects be encoded, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Person> personFlux = ... ;
|
||||
|
||||
@@ -43,8 +52,10 @@ You can also have a stream of objects be encoded, as the following example shows
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val people: Flow<Person> = ...
|
||||
|
||||
@@ -55,12 +66,16 @@ You can also have a stream of objects be encoded, as the following example shows
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, if you have the actual value, you can use the `bodyValue` shortcut method,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Person person = ... ;
|
||||
|
||||
@@ -71,8 +86,10 @@ as the following example shows:
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person: Person = ...
|
||||
|
||||
@@ -83,6 +100,7 @@ as the following example shows:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -93,8 +111,11 @@ To send form data, you can provide a `MultiValueMap<String, String>` as the body
|
||||
content is automatically set to `application/x-www-form-urlencoded` by the
|
||||
`FormHttpMessageWriter`. The following example shows how to use `MultiValueMap<String, String>`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MultiValueMap<String, String> formData = ... ;
|
||||
|
||||
@@ -104,8 +125,10 @@ content is automatically set to `application/x-www-form-urlencoded` by the
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val formData: MultiValueMap<String, String> = ...
|
||||
|
||||
@@ -115,11 +138,15 @@ content is automatically set to `application/x-www-form-urlencoded` by the
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
You can also supply form data in-line by using `BodyInserters`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.BodyInserters.*;
|
||||
|
||||
@@ -129,8 +156,10 @@ You can also supply form data in-line by using `BodyInserters`, as the following
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.reactive.function.BodyInserters.*
|
||||
|
||||
@@ -140,6 +169,7 @@ You can also supply form data in-line by using `BodyInserters`, as the following
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -151,8 +181,11 @@ either `Object` instances that represent part content or `HttpEntity` instances
|
||||
headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a
|
||||
multipart request. The following example shows how to create a `MultiValueMap<String, ?>`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MultipartBodyBuilder builder = new MultipartBodyBuilder();
|
||||
builder.part("fieldPart", "fieldValue");
|
||||
@@ -162,8 +195,10 @@ multipart request. The following example shows how to create a `MultiValueMap<St
|
||||
|
||||
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val builder = MultipartBodyBuilder().apply {
|
||||
part("fieldPart", "fieldValue")
|
||||
@@ -174,6 +209,7 @@ multipart request. The following example shows how to create a `MultiValueMap<St
|
||||
|
||||
val parts = builder.build()
|
||||
----
|
||||
======
|
||||
|
||||
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
|
||||
@@ -184,8 +220,11 @@ builder `part` methods.
|
||||
Once a `MultiValueMap` is prepared, the easiest way to pass it to the `WebClient` is
|
||||
through the `body` method, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MultipartBodyBuilder builder = ...;
|
||||
|
||||
@@ -195,8 +234,10 @@ through the `body` method, as the following example shows:
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val builder: MultipartBodyBuilder = ...
|
||||
|
||||
@@ -206,6 +247,7 @@ through the `body` method, as the following example shows:
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, which could also
|
||||
represent regular form data (that is, `application/x-www-form-urlencoded`), you need not
|
||||
@@ -215,8 +257,11 @@ set the `Content-Type` to `multipart/form-data`. This is always the case when us
|
||||
As an alternative to `MultipartBodyBuilder`, you can also provide multipart content,
|
||||
inline-style, through the built-in `BodyInserters`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.BodyInserters.*;
|
||||
|
||||
@@ -226,8 +271,10 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
|
||||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.reactive.function.BodyInserters.*
|
||||
|
||||
@@ -237,6 +284,7 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
|
||||
.retrieve()
|
||||
.awaitBody<Unit>()
|
||||
----
|
||||
======
|
||||
|
||||
[[partevent]]
|
||||
=== `PartEvent`
|
||||
@@ -252,8 +300,11 @@ the `WebClient`.
|
||||
|
||||
For instance, this sample will POST a multipart form containing a form field and a file.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Resource resource = ...
|
||||
Mono<String> result = webClient
|
||||
@@ -266,8 +317,10 @@ Mono<String> result = webClient
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
var resource: Resource = ...
|
||||
var result: Mono<String> = webClient
|
||||
@@ -282,6 +335,7 @@ var result: Mono<String> = webClient
|
||||
.retrieve()
|
||||
.bodyToMono()
|
||||
----
|
||||
======
|
||||
|
||||
On the server side, `PartEvent` objects that are received via `@RequestBody` or
|
||||
`ServerRequest::bodyToFlux(PartEvent.class)` can be relayed to another service
|
||||
|
||||
@@ -21,26 +21,35 @@ You can also use `WebClient.builder()` with further options:
|
||||
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.codecs(configurer -> ... )
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
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"]
|
||||
.Java
|
||||
----
|
||||
WebClient client1 = WebClient.builder()
|
||||
.filter(filterA).filter(filterB).build();
|
||||
@@ -52,8 +61,10 @@ modified copy as follows:
|
||||
|
||||
// client2 has filterA, filterB, filterC, filterD
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client1 = WebClient.builder()
|
||||
.filter(filterA).filter(filterB).build()
|
||||
@@ -65,6 +76,7 @@ modified copy as follows:
|
||||
|
||||
// client2 has filterA, filterB, filterC, filterD
|
||||
----
|
||||
======
|
||||
|
||||
[[webflux-client-builder-maxinmemorysize]]
|
||||
== MaxInMemorySize
|
||||
@@ -79,20 +91,26 @@ org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on m
|
||||
|
||||
To change the limit for default codecs, use the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient webClient = WebClient.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val webClient = WebClient.builder()
|
||||
.codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) }
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -101,8 +119,11 @@ To change the limit for default codecs, use the following:
|
||||
|
||||
To customize Reactor Netty settings, provide a pre-configured `HttpClient`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);
|
||||
|
||||
@@ -110,8 +131,10 @@ To customize Reactor Netty settings, provide a pre-configured `HttpClient`:
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val httpClient = HttpClient.create().secure { ... }
|
||||
|
||||
@@ -119,6 +142,7 @@ To customize Reactor Netty settings, provide a pre-configured `HttpClient`:
|
||||
.clientConnector(ReactorClientHttpConnector(httpClient))
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-client-builder-reactor-resources]]
|
||||
@@ -137,20 +161,26 @@ Netty global resources are shut down when the Spring `ApplicationContext` is clo
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
public ReactorResourceFactory reactorResourceFactory() {
|
||||
return new ReactorResourceFactory();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Bean
|
||||
fun reactorResourceFactory() = ReactorResourceFactory()
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
You can also choose not to participate in the global Reactor Netty resources. However,
|
||||
@@ -158,8 +188,11 @@ in this mode, the burden is on you to ensure that all Reactor Netty client and s
|
||||
instances use shared resources, as the following example shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
public ReactorResourceFactory resourceFactory() {
|
||||
@@ -181,6 +214,7 @@ instances use shared resources, as the following example shows:
|
||||
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`.
|
||||
@@ -216,8 +250,11 @@ instances use shared resources, as the following example shows:
|
||||
|
||||
To configure a connection timeout:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import io.netty.channel.ChannelOption;
|
||||
|
||||
@@ -228,8 +265,10 @@ To configure a connection timeout:
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import io.netty.channel.ChannelOption
|
||||
|
||||
@@ -240,11 +279,15 @@ To configure a connection timeout:
|
||||
.clientConnector(ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
|
||||
To configure a read or write timeout:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||
@@ -257,8 +300,10 @@ To configure a read or write timeout:
|
||||
// Create WebClient...
|
||||
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler
|
||||
@@ -271,30 +316,40 @@ To configure a read or write timeout:
|
||||
|
||||
// Create WebClient...
|
||||
----
|
||||
======
|
||||
|
||||
To configure a response timeout for all requests:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.responseTimeout(Duration.ofSeconds(2));
|
||||
|
||||
// Create WebClient...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
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"]
|
||||
.Java
|
||||
----
|
||||
WebClient.create().get()
|
||||
.uri("https://example.org/path")
|
||||
@@ -305,8 +360,10 @@ To configure a response timeout for a specific request:
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
WebClient.create().get()
|
||||
.uri("https://example.org/path")
|
||||
@@ -317,6 +374,7 @@ To configure a response timeout for a specific request:
|
||||
.retrieve()
|
||||
.bodyToMono(String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -325,8 +383,11 @@ To configure a response timeout for a specific request:
|
||||
|
||||
The following example shows how to customize the JDK `HttpClient`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
@@ -339,8 +400,9 @@ The following example shows how to customize the JDK `HttpClient`:
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
@@ -351,6 +413,7 @@ The following example shows how to customize the JDK `HttpClient`:
|
||||
|
||||
val webClient = WebClient.builder().clientConnector(connector).build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -360,8 +423,11 @@ The following example shows how to customize the JDK `HttpClient`:
|
||||
The following example shows how to customize Jetty `HttpClient` settings:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.setCookieStore(...);
|
||||
@@ -370,8 +436,10 @@ The following example shows how to customize Jetty `HttpClient` settings:
|
||||
.clientConnector(new JettyClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val httpClient = HttpClient()
|
||||
httpClient.cookieStore = ...
|
||||
@@ -380,6 +448,7 @@ The following example shows how to customize Jetty `HttpClient` settings:
|
||||
.clientConnector(JettyClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`),
|
||||
@@ -391,8 +460,11 @@ declaring a Spring-managed bean of type `JettyResourceFactory`, as the following
|
||||
shows:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
public JettyResourceFactory resourceFactory() {
|
||||
@@ -411,6 +483,7 @@ shows:
|
||||
return WebClient.builder().clientConnector(connector).build(); <2>
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> Use the `JettyClientHttpConnector` constructor with resource factory.
|
||||
<2> Plug the connector into the `WebClient.Builder`.
|
||||
|
||||
@@ -442,8 +515,11 @@ shows:
|
||||
|
||||
The following example shows how to customize Apache HttpComponents `HttpClient` settings:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
|
||||
clientBuilder.setDefaultRequestConfig(...);
|
||||
@@ -453,8 +529,10 @@ The following example shows how to customize Apache HttpComponents `HttpClient`
|
||||
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = HttpAsyncClients.custom().apply {
|
||||
setDefaultRequestConfig(...)
|
||||
@@ -462,5 +540,6 @@ The following example shows how to customize Apache HttpComponents `HttpClient`
|
||||
val connector = HttpComponentsClientHttpConnector(client)
|
||||
val webClient = WebClient.builder().clientConnector(connector).build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -9,8 +9,11 @@ e.g. via `concatMap`, then you'll need to use the Reactor `Context`.
|
||||
The Reactor `Context` needs to be populated at the end of a reactive chain in order to
|
||||
apply to all operations. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.filter((request, next) ->
|
||||
@@ -28,6 +31,7 @@ apply to all operations. For example:
|
||||
})
|
||||
.contextWrite(context -> context.put("foo", ...));
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ The `exchangeToMono()` and `exchangeToFlux()` methods (or `awaitExchange { }` an
|
||||
are useful for more advanced cases that require more control, such as to decode the response differently
|
||||
depending on the response status:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> entityMono = client.get()
|
||||
.uri("/persons/1")
|
||||
@@ -21,8 +24,10 @@ depending on the response status:
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val entity = client.get()
|
||||
.uri("/persons/1")
|
||||
@@ -36,6 +41,7 @@ val entity = client.get()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
When using the above, after the returned `Mono` or `Flux` completes, the response body
|
||||
is checked and if not consumed it is released to prevent memory and connection leaks.
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder`
|
||||
in order to intercept and modify requests, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.builder()
|
||||
.filter((request, next) -> {
|
||||
@@ -18,8 +21,10 @@ in order to intercept and modify requests, as the following example shows:
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.builder()
|
||||
.filter { request, next ->
|
||||
@@ -32,12 +37,16 @@ in order to intercept and modify requests, as the following example shows:
|
||||
}
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
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:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
|
||||
|
||||
@@ -45,8 +54,10 @@ a filter for basic authentication through a static factory method:
|
||||
.filter(basicAuthentication("user", "password"))
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication
|
||||
|
||||
@@ -54,12 +65,16 @@ a filter for basic authentication through a static factory method:
|
||||
.filter(basicAuthentication("user", "password"))
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
Filters can be added or removed by mutating an existing `WebClient` instance, resulting
|
||||
in a new `WebClient` instance that does not affect the original one. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
|
||||
|
||||
@@ -69,13 +84,16 @@ in a new `WebClient` instance that does not affect the original one. For example
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = webClient.mutate()
|
||||
.filters { it.add(0, basicAuthentication("user", "password")) }
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
`WebClient` is a thin facade around the chain of filters followed by an
|
||||
`ExchangeFunction`. It provides a workflow to make requests, to encode to and from higher
|
||||
@@ -85,8 +103,11 @@ its content or to otherwise propagate it downstream to the `WebClient` which wil
|
||||
the same. Below is a filter that handles the `UNAUTHORIZED` status code but ensures that
|
||||
any response content, whether expected or not, is released:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public ExchangeFilterFunction renewTokenFilter() {
|
||||
return (request, next) -> next.exchange(request).flatMap(response -> {
|
||||
@@ -103,8 +124,10 @@ any response content, whether expected or not, is released:
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
fun renewTokenFilter(): ExchangeFilterFunction? {
|
||||
return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction ->
|
||||
@@ -123,6 +146,7 @@ any response content, whether expected or not, is released:
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
The `retrieve()` method can be used to declare how to extract the response. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.create("https://example.org");
|
||||
|
||||
@@ -13,8 +16,10 @@ The `retrieve()` method can be used to declare how to extract the response. For
|
||||
.retrieve()
|
||||
.toEntity(Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.create("https://example.org")
|
||||
|
||||
@@ -23,11 +28,15 @@ The `retrieve()` method can be used to declare how to extract the response. For
|
||||
.retrieve()
|
||||
.toEntity<Person>().awaitSingle()
|
||||
----
|
||||
======
|
||||
|
||||
Or to get only the body:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebClient client = WebClient.create("https://example.org");
|
||||
|
||||
@@ -36,8 +45,10 @@ Or to get only the body:
|
||||
.retrieve()
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebClient.create("https://example.org")
|
||||
|
||||
@@ -46,32 +57,42 @@ Or to get only the body:
|
||||
.retrieve()
|
||||
.awaitBody<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
To get a stream of decoded objects:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Quote> result = client.get()
|
||||
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.retrieve()
|
||||
.bodyToFlux(Quote.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val result = client.get()
|
||||
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.retrieve()
|
||||
.bodyToFlow<Quote>()
|
||||
----
|
||||
======
|
||||
|
||||
By default, 4xx or 5xx responses result in an `WebClientResponseException`, including
|
||||
sub-classes for specific HTTP status codes. To customize the handling of error
|
||||
responses, use `onStatus` handlers as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
@@ -80,8 +101,10 @@ responses, use `onStatus` handlers as follows:
|
||||
.onStatus(HttpStatus::is5xxServerError, response -> ...)
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
@@ -90,6 +113,7 @@ responses, use `onStatus` handlers as follows:
|
||||
.onStatus(HttpStatus::is5xxServerError) { ... }
|
||||
.awaitBody<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
`WebClient` can be used in synchronous style by blocking at the end for the result:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Person person = client.get().uri("/person/{id}", i).retrieve()
|
||||
.bodyToMono(Person.class)
|
||||
@@ -15,8 +18,10 @@
|
||||
.collectList()
|
||||
.block();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person = runBlocking {
|
||||
client.get().uri("/person/{id}", i).retrieve()
|
||||
@@ -29,12 +34,16 @@
|
||||
.toList()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
However if multiple calls need to be made, it's more efficient to avoid blocking on each
|
||||
response individually, and instead wait for the combined result:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
|
||||
.retrieve().bodyToMono(Person.class);
|
||||
@@ -50,8 +59,10 @@ response individually, and instead wait for the combined result:
|
||||
})
|
||||
.block();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val data = runBlocking {
|
||||
val personDeferred = async {
|
||||
@@ -67,6 +78,7 @@ response individually, and instead wait for the combined result:
|
||||
mapOf("person" to personDeferred.await(), "hobbies" to hobbiesDeferred.await())
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The above is merely one example. There are lots of other patterns and operators for putting
|
||||
together a reactive pipeline that makes many remote calls, potentially some nested,
|
||||
|
||||
Reference in New Issue
Block a user