diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index 0c430af97ce..294af0b4fc0 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -946,50 +946,24 @@ plugging in third-party or custom solutions here. [[rest-client-access]] -=== Accessing RESTful services on the Client -The `RestTemplate` is the core class for client-side access to RESTful services. It is -conceptually similar to other template classes in Spring, such as `JdbcTemplate` and -`JmsTemplate` and other template classes found in other Spring portfolio projects. -`RestTemplate`'s behavior is customized by providing callback methods and configuring -the `HttpMessageConverter` used to marshal objects into the HTTP request body and to -unmarshal any response back into an object. As it is common to use XML as a message -format, Spring provides a `MarshallingHttpMessageConverter` that uses the Object-to-XML -framework that is part of the `org.springframework.oxm` package. This gives you a wide -range of choices of XML to Object mapping technologies to choose from. +=== Accessing REST endpoints -This section describes how to use the `RestTemplate` and its associated -`HttpMessageConverters`. +The Spring Framework offers two choices for client-side access to REST endpoints: +* <> -- the original Spring REST client with an API similar to other +template classes in Spring, such as `JdbcTemplate`, `JmsTemplate` and others. The +`RestTemplate` is built for synchronous use with the blocking I/O. +* <> -- reactive client with a functional, +fluent API. It is built on a non-blocking foundation for async and sync scenarios and +supports Reactive Streams back pressure. [[rest-resttemplate]] ==== RestTemplate -Invoking RESTful services in Java is typically done using a helper class such as Apache -HttpComponents `HttpClient`. For common REST operations this approach is too low level as -shown below. -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - String uri = "http://example.com/hotels/1/bookings"; - - PostMethod post = new PostMethod(uri); - String request = // create booking request content - post.setRequestEntity(new StringRequestEntity(request)); - - httpClient.executeMethod(post); - - if (HttpStatus.SC_CREATED == post.getStatusCode()) { - Header location = post.getRequestHeader("Location"); - if (location != null) { - System.out.println("Created new booking at :" + location.getValue()); - } - } ----- - -RestTemplate provides higher level methods that correspond to each of the six main HTTP -methods that make invoking many RESTful services a one-liner and enforce REST best -practices. +The `RestTemplate` provides a higher level API over HTTP client libraries with methods +that correspond to each of the six main HTTP methods that make invoking many RESTful +services a one-liner and enforce REST best practices. [NOTE] @@ -1373,6 +1347,8 @@ The `AsyncRestTemplate` is deprecated. Please use the <> instead. +include::web/webflux-client.adoc[leveloffset=+3] + [[ejb]] diff --git a/src/docs/asciidoc/web/webflux-client.adoc b/src/docs/asciidoc/web/webflux-client.adoc new file mode 100644 index 00000000000..9b2db5da50f --- /dev/null +++ b/src/docs/asciidoc/web/webflux-client.adoc @@ -0,0 +1,260 @@ +[[webflux-client]] += WebClient + +The `spring-webflux` module includes a non-blocking, reactive client for HTTP requests +with Reactive Streams back pressure. It shares +<> and other infrastructure with the +server <>. + +`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 +<>). + +[TIP] +==== +By comparison to the +<>, 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 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 result = client.get() + .uri("/quotes").accept(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 result = client.get() + .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) + .retrieve() + .onStatus(HttpStatus::is4xxServerError, response -> ...) + .onStatus(HttpStatus::is5xxServerError, response -> ...) + .bodyToFlux(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 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> result = client.get() + .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) + .exchange() + .flatMap(response -> response.bodyToEntity(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 you use `exchange()`, you must call `response.close()` if you do not intend to read +the response body in order to close the underlying HTTP connection. Not doing so can +result in connection pool inconsistencies or memory leaks. + +You do not have to call `response.close()` if you consume the body because forcing a +connection to be closed negates the benefits of persistent connections and connection +pooling. +==== + + +[[webflux-client-body]] +== Request body + +The request body can be encoded from an Object: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + Mono personMono = ... ; + + Mono 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 personFlux = ... ; + + Mono 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 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 <> 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 <>. + +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(); +---- + diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index 8831248cfde..3d66e23b303 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -1386,265 +1386,7 @@ from the base class and you can still have any number of other ``WebMvcConfigure the classpath. -[[webflux-client]] -== WebClient - -The `spring-webflux` module includes a non-blocking, reactive client for HTTP requests -with Reactive Streams back pressure. It shares <> and other -infrastructure with the server <>. - -`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 -<>). - -[TIP] -==== -By comparison to the -<>, 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 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 result = client.get() - .uri("/quotes").accept(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 result = client.get() - .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) - .retrieve() - .onStatus(HttpStatus::is4xxServerError, response -> ...) - .onStatus(HttpStatus::is5xxServerError, response -> ...) - .bodyToFlux(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 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> result = client.get() - .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) - .exchange() - .flatMap(response -> response.bodyToEntity(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 you use `exchange()`, you must call `response.close()` if you do not intend to read -the response body in order to close the underlying HTTP connection. Not doing so can -result in connection pool inconsistencies or memory leaks. - -You do not have to call `response.close()` if you consume the body because forcing a -connection to be closed negates the benefits of persistent connections and connection -pooling. -==== - - -[[webflux-client-body]] -=== Request body - -The request body can be encoded from an Object: - -[source,java,intent=0] -[subs="verbatim,quotes"] ----- - Mono personMono = ... ; - - Mono 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 personFlux = ... ; - - Mono 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 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 <> 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 <>. - -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(); ----- - - +include::webflux-client.adoc[leveloffset=+1] [[webflux-reactive-libraries]]