From 67910ee48cd285a6f5367a4206dec8fcf935d056 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 27 Sep 2017 23:34:45 -0400 Subject: [PATCH] WebFlux and Integration share webflux-client.adoc Extract WebClient content into a separate file that is now included both in the WebFlux and in the Integration sections. This allows having RestTemplate and WebClient documented in one place under Integration while also keeping the same included in the WebFlux section too. --- src/docs/asciidoc/integration.adoc | 50 ++--- src/docs/asciidoc/web/webflux-client.adoc | 260 ++++++++++++++++++++++ src/docs/asciidoc/web/webflux.adoc | 260 +--------------------- 3 files changed, 274 insertions(+), 296 deletions(-) create mode 100644 src/docs/asciidoc/web/webflux-client.adoc 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]]