diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 3f283003f14..ff76e02e04d 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -443,8 +443,8 @@ public interface WebTestClient { Builder responseTimeout(Duration timeout); /** - * Shortcut for pre-packaged customizations to WebTestClient builder. - * @param configurer the configurer to apply + * Apply the given {@code Consumer} to this builder instance. + *

This can be useful for applying pre-packaged customizations. */ Builder apply(WebTestClientConfigurer configurer); diff --git a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java index 19fa80c6a0a..c16d5908435 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java @@ -23,6 +23,33 @@ import org.springframework.core.codec.Encoder; * Extension of {@link CodecConfigurer} for HTTP message reader and writer * options relevant on the client side. * + *

HTTP message readers for the following are registered by default: + *

+ * + *

HTTP message writers registered by default: + *

+ * * @author Rossen Stoyanchev * @since 5.0 */ diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java index 74776f53b58..10861f422fa 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java @@ -22,6 +22,33 @@ import org.springframework.core.codec.Encoder; * Extension of {@link CodecConfigurer} for HTTP message reader and writer * options relevant on the server side. * + *

HTTP message readers for the following are registered by default: + *

+ * + *

HTTP message writers registered by default: + *

+ * * @author Rossen Stoyanchev * @since 5.0 */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java index c295d2cf5dd..31dfe5742fb 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java @@ -40,8 +40,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** - * Implementations of {@link BodyInserter} that write various bodies, such a reactive streams, - * server-sent events, resources, etc. + * Static factory methods for {@link BodyInserter} implementations. * * @author Arjen Poutsma * @author Rossen Stoyanchev diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java index eaff0e8dbe1..51d11101f7c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java @@ -33,6 +33,15 @@ import org.springframework.http.codec.HttpMessageWriter; */ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Builder { + final static ExchangeStrategies DEFAULT_EXCHANGE_STRATEGIES; + + static { + DefaultExchangeStrategiesBuilder builder = new DefaultExchangeStrategiesBuilder(); + builder.defaultConfiguration(); + DEFAULT_EXCHANGE_STRATEGIES = builder.build(); + } + + private final ClientCodecConfigurer codecConfigurer = ClientCodecConfigurer.create(); @@ -53,36 +62,36 @@ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Build @Override public ExchangeStrategies build() { - return new DefaultExchangeStrategies(this.codecConfigurer.getReaders(), - this.codecConfigurer.getWriters()); + return new DefaultExchangeStrategies( + this.codecConfigurer.getReaders(), this.codecConfigurer.getWriters()); } private static class DefaultExchangeStrategies implements ExchangeStrategies { - private final List> messageReaders; + private final List> readers; - private final List> messageWriters; + private final List> writers; - public DefaultExchangeStrategies( - List> messageReaders, List> messageWriters) { - this.messageReaders = unmodifiableCopy(messageReaders); - this.messageWriters = unmodifiableCopy(messageWriters); + public DefaultExchangeStrategies(List> readers, List> writers) { + this.readers = unmodifiableCopy(readers); + this.writers = unmodifiableCopy(writers); } private static List unmodifiableCopy(List list) { return Collections.unmodifiableList(new ArrayList<>(list)); } + @Override public List> messageReaders() { - return this.messageReaders; + return this.readers; } @Override public List> messageWriters() { - return this.messageWriters; + return this.writers; } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java index 24ca1352ea2..027fdd5c99a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java @@ -16,18 +16,19 @@ package org.springframework.web.reactive.function.client; +import java.net.URI; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.util.Assert; /** - * Exposes request-response exchange functionality, such as to - * {@linkplain #create(ClientHttpConnector) create} an {@code ExchangeFunction} given a - * {@code ClientHttpConnector}. + * Static factory methods to create an {@link ExchangeFunction}. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -39,24 +40,25 @@ public abstract class ExchangeFunctions { /** - * Create a new {@link ExchangeFunction} with the given connector. This method uses - * {@linkplain ExchangeStrategies#withDefaults() default strategies}. - * @param connector the connector to create connections - * @return the created function + * Create an {@code ExchangeFunction} with the given {@code ClientHttpConnector}. + * This is the same as calling + * {@link #create(ClientHttpConnector, ExchangeStrategies)} and passing + * {@link ExchangeStrategies#withDefaults()}. + * @param connector the connector to use for connecting to servers + * @return the created {@code ExchangeFunction} */ public static ExchangeFunction create(ClientHttpConnector connector) { return create(connector, ExchangeStrategies.withDefaults()); } /** - * Create a new {@link ExchangeFunction} with the given connector and strategies. - * @param connector the connector to create connections - * @param strategies the strategies to use - * @return the created function + * Create an {@code ExchangeFunction} with the given + * {@code ClientHttpConnector} and {@code ExchangeStrategies}. + * @param connector the connector to use for connecting to servers + * @param strategies the {@code ExchangeStrategies} to use + * @return the created {@code ExchangeFunction} */ public static ExchangeFunction create(ClientHttpConnector connector, ExchangeStrategies strategies) { - Assert.notNull(connector, "ClientHttpConnector must not be null"); - Assert.notNull(strategies, "ExchangeStrategies must not be null"); return new DefaultExchangeFunction(connector, strategies); } @@ -67,26 +69,33 @@ public abstract class ExchangeFunctions { private final ExchangeStrategies strategies; + public DefaultExchangeFunction(ClientHttpConnector connector, ExchangeStrategies strategies) { + Assert.notNull(connector, "ClientHttpConnector must not be null"); + Assert.notNull(strategies, "ExchangeStrategies must not be null"); this.connector = connector; this.strategies = strategies; } + @Override public Mono exchange(ClientRequest request) { Assert.notNull(request, "ClientRequest must not be null"); + + HttpMethod httpMethod = request.method(); + URI url = request.url(); + return this.connector - .connect(request.method(), request.url(), - clientHttpRequest -> request.writeTo(clientHttpRequest, this.strategies)) + .connect(httpMethod, url, httpRequest -> request.writeTo(httpRequest, this.strategies)) .doOnSubscribe(subscription -> logger.debug("Subscriber present")) .doOnRequest(n -> logger.debug("Demand signaled")) .doOnCancel(() -> logger.debug("Cancelling request")) .map(response -> { if (logger.isDebugEnabled()) { - int status = response.getRawStatusCode(); - HttpStatus resolvedStatus = HttpStatus.resolve(status); - logger.debug("Response received, status: " + status + - (resolvedStatus != null ? " " + resolvedStatus.getReasonPhrase() : "")); + int code = response.getRawStatusCode(); + HttpStatus status = HttpStatus.resolve(code); + String reason = status != null ? " " + status.getReasonPhrase() : ""; + logger.debug("Response received, status: " + code + reason); } return new DefaultClientResponse(response, this.strategies); }); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java index aa25c00ec09..33e5eee95c2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java @@ -24,10 +24,10 @@ import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; /** - * Defines the strategies for invoking {@link ExchangeFunction}s. An instance of - * this class is immutable; instances are typically created through the mutable {@link Builder}: - * either through {@link #builder()} to set up default strategies, or {@link #empty()} to start - * from scratch. + * Provides strategies for use in an {@link ExchangeFunction}. + * + *

To create an instance, see the static methods {@link #withDefaults()}, + * {@link #builder()}, and {@link #empty()}. * * @author Brian Clozel * @author Arjen Poutsma @@ -36,13 +36,13 @@ import org.springframework.http.codec.HttpMessageWriter; public interface ExchangeStrategies { /** - * Return the {@link HttpMessageReader}s to be used for request body conversion. + * Return {@link HttpMessageReader}s to read and decode the response body with. * @return the stream of message readers */ List> messageReaders(); /** - * Return the {@link HttpMessageWriter}s to be used for response body conversion. + * Return {@link HttpMessageWriter}s to write and encode the request body with. * @return the stream of message writers */ List> messageWriters(); @@ -51,16 +51,17 @@ public interface ExchangeStrategies { // Static methods /** - * Return a new {@code ExchangeStrategies} with default initialization. - * @return the new {@code ExchangeStrategies} + * Return a new {@code ExchangeStrategies} with default configuration + * provided by {@link ClientCodecConfigurer}. */ static ExchangeStrategies withDefaults() { - return builder().build(); + return DefaultExchangeStrategiesBuilder.DEFAULT_EXCHANGE_STRATEGIES; } /** - * Return a mutable builder for a {@code ExchangeStrategies} with default initialization. - * @return the builder + * Return a builder pre-configured with default configuration to start. + * This is the same as {@link #withDefaults()} but returns a mutable builder + * for further customizations. */ static Builder builder() { DefaultExchangeStrategiesBuilder builder = new DefaultExchangeStrategiesBuilder(); @@ -69,8 +70,7 @@ public interface ExchangeStrategies { } /** - * Return a mutable, empty builder for a {@code ExchangeStrategies}. - * @return the builder + * Return a builder with empty configuration to start. */ static Builder empty() { return new DefaultExchangeStrategiesBuilder(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index 657b8f70ddd..76fc0c86fd8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -52,13 +52,17 @@ import org.springframework.web.util.UriBuilderFactory; * {@link #create(String)} or obtain a {@link WebClient#builder()} to create an * instance. * - *

For examples with a response body see - * {@link RequestHeadersSpec#retrieve() retrieve()} and - * {@link RequestHeadersSpec#exchange() exchange()}. - * For examples with a request body see - * {@link RequestBodySpec#body(Publisher, Class) body(Publisher,Class)}, - * {@link RequestBodySpec#syncBody(Object) syncBody(Object)}, and - * {@link RequestBodySpec#body(BodyInserter) body(BodyInserter)}. + *

For examples with a response body, see the Javadoc for: + *

    + *
  • {@link RequestHeadersSpec#retrieve() retrieve()} + *
  • {@link RequestHeadersSpec#exchange() exchange()} + *
+ * For examples with a request body see: + *
    + *
  • {@link RequestBodySpec#body(Publisher, Class) body(Publisher,Class)} + *
  • {@link RequestBodySpec#syncBody(Object) syncBody(Object)} + *
  • {@link RequestBodySpec#body(BodyInserter) body(BodyInserter)} + *
* * @author Rossen Stoyanchev * @author Arjen Poutsma @@ -67,57 +71,57 @@ import org.springframework.web.util.UriBuilderFactory; public interface WebClient { /** - * Prepare an HTTP GET request. + * Start building an HTTP GET request. * @return a spec for specifying the target URL */ RequestHeadersUriSpec get(); /** - * Prepare an HTTP HEAD request. + * Start building an HTTP HEAD request. * @return a spec for specifying the target URL */ RequestHeadersUriSpec head(); /** - * Prepare an HTTP POST request. + * Start building an HTTP POST request. * @return a spec for specifying the target URL */ RequestBodyUriSpec post(); /** - * Prepare an HTTP PUT request. + * Start building an HTTP PUT request. * @return a spec for specifying the target URL */ RequestBodyUriSpec put(); /** - * Prepare an HTTP PATCH request. + * Start building an HTTP PATCH request. * @return a spec for specifying the target URL */ RequestBodyUriSpec patch(); /** - * Prepare an HTTP DELETE request. + * Start building an HTTP DELETE request. * @return a spec for specifying the target URL */ RequestHeadersUriSpec delete(); /** - * Prepare an HTTP OPTIONS request. + * Start building an HTTP OPTIONS request. * @return a spec for specifying the target URL */ RequestHeadersUriSpec options(); /** - * Prepare a request for the specified {@code HttpMethod}. + * Start building a request for the given {@code HttpMethod}. * @return a spec for specifying the target URL */ RequestBodyUriSpec method(HttpMethod method); /** - * Return a builder for a new {@code WebClient} with properties replicated - * from the current {@code WebClient} instance, but without affecting it. + * Return a builder to create a new {@code WebClient} whose settings are + * replicated from the current {@code WebClient}. */ Builder mutate(); @@ -125,7 +129,7 @@ public interface WebClient { // Static, factory methods /** - * Create a new {@code WebClient} with a Reactor Netty connector. + * Create a new {@code WebClient} with Reactor Netty by default. * @see #create(String) * @see #builder() */ @@ -134,7 +138,7 @@ public interface WebClient { } /** - * A variant of {@link #create()} that accepts a default base URL. For more + * Variant of {@link #create()} that accepts a default base URL. For more * details see {@link Builder#baseUrl(String) Builder.baseUrl(String)}. * @param baseUrl the base URI for all requests * @see #builder() @@ -161,27 +165,25 @@ public interface WebClient { * *

For example given base URL "http://abc.com/v1": *

-		 * Mono<Account> result = client.get()
-		 *         .uri("/accounts/{id}", 43)
-		 *         .exchange()
-		 *         .then(response -> response.bodyToMono(Account.class));
+		 * Mono<Account> result = client.get().uri("/accounts/{id}", 43)
+		 *         .retrieve()
+		 *         .bodyToMono(Account.class);
 		 *
 		 * // Result: http://abc.com/v1/accounts/43
 		 *
 		 * Flux<Account> result = client.get()
 		 *         .uri(builder -> builder.path("/accounts").queryParam("q", "12").build())
-		 *         .exchange()
-		 *         .then(response -> response.bodyToFlux(Account.class));
+		 *         .retrieve()
+		 *         .bodyToFlux(Account.class);
 		 *
 		 * // Result: http://abc.com/v1/accounts?q=12
 		 * 
* *

The base URL can be overridden with an absolute URI: *

-		 * Mono<Account> result = client.get()
-		 *         .uri("http://xyz.com/path")
-		 *         .exchange()
-		 *         .then(response -> response.bodyToMono(Account.class));
+		 * Mono<Account> result = client.get().uri("http://xyz.com/path")
+		 *         .retrieve()
+		 *         .bodyToMono(Account.class);
 		 *
 		 * // Result: http://xyz.com/path
 		 * 
@@ -190,8 +192,8 @@ public interface WebClient { *
 		 * Flux<Account> result = client.get()
 		 *         .uri(builder -> builder.replacePath("/v2/accounts").queryParam("q", "12").build())
-		 *         .exchange()
-		 *         .then(response -> response.bodyToFlux(Account.class));
+		 *         .retrieve()
+		 *         .bodyToFlux(Account.class);
 		 *
 		 * // Result: http://abc.com/v2/accounts?q=12
 		 * 
@@ -232,12 +234,9 @@ public interface WebClient { /** * Manipulate the default headers with the given consumer. The - * headers provided to the consumer are "live", so that the consumer can be used to - * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, - * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other - * {@link HttpHeaders} methods. - * @param headersConsumer a function that consumes the {@code HttpHeaders} - * @return this builder + * headers provided to the consumer are "live", so that the consumer + * can be used to overwrite or remove existing values. + * @param headersConsumer the headers consumer */ Builder defaultHeaders(Consumer headersConsumer); @@ -250,28 +249,12 @@ public interface WebClient { /** * Manipulate the default cookies with the given consumer. The - * map provided to the consumer is "live", so that the consumer can be used to - * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, - * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other - * {@link MultiValueMap} methods. + * cookies provided to the consumer are "live", so that the consumer + * can be used to overwrite or remove existing values. * @param cookiesConsumer a function that consumes the cookies map - * @return this builder */ Builder defaultCookies(Consumer> cookiesConsumer); - /** - * Configure the {@link ClientHttpConnector} to use. - *

By default an instance of - * {@link org.springframework.http.client.reactive.ReactorClientHttpConnector - * ReactorClientHttpConnector} is created if this is not set. However a - * shared instance may be passed instead, e.g. for use with multiple - * {@code WebClient}'s targeting different base URIs. - * @param connector the connector to use - * @see #exchangeStrategies(ExchangeStrategies) - * @see #exchangeFunction(ExchangeFunction) - */ - Builder clientConnector(ClientHttpConnector connector); - /** * Add the given filter to the filter chain. * @param filter the filter to be added to the chain @@ -279,8 +262,8 @@ public interface WebClient { Builder filter(ExchangeFilterFunction filter); /** - * Manipulate the filters with the given consumer. The - * list provided to the consumer is "live", so that the consumer can be used to remove + * Manipulate the filters with the given consumer. The list provided to + * the consumer is "live", so that the consumer can be used to remove * filters, change ordering, etc. * @param filtersConsumer a function that consumes the filter list * @return this builder @@ -288,34 +271,40 @@ public interface WebClient { Builder filters(Consumer> filtersConsumer); /** - * Provide a pre-configured {@link ExchangeFunction} instance. This is - * an alternative to and effectively overrides the following: - *

    - *
  • {@link #clientConnector(ClientHttpConnector)} - *
  • {@link #exchangeStrategies(ExchangeStrategies)}. - *
- * @param exchangeFunction the exchange function to use - * @see #clientConnector(ClientHttpConnector) - * @see #exchangeStrategies(ExchangeStrategies) + * Configure the {@link ClientHttpConnector} to use. This is useful for + * plugging in and/or customizing options of the underlying HTTP client + * library (e.g. SSL). + *

By default this is set to + * {@link org.springframework.http.client.reactive.ReactorClientHttpConnector + * ReactorClientHttpConnector}. + * @param connector the connector to use */ - Builder exchangeFunction(ExchangeFunction exchangeFunction); + Builder clientConnector(ClientHttpConnector connector); /** * Configure the {@link ExchangeStrategies} to use. - *

By default {@link ExchangeStrategies#withDefaults()} is used. + *

By default this is obtained from {@link ExchangeStrategies#withDefaults()}. * @param strategies the strategies to use - * @see #clientConnector(ClientHttpConnector) - * @see #exchangeFunction(ExchangeFunction) */ Builder exchangeStrategies(ExchangeStrategies strategies); + /** + * Provide an {@link ExchangeFunction} pre-configured with + * {@link ClientHttpConnector} and {@link ExchangeStrategies}. + *

This is an alternative to, and effectively overrides + * {@link #clientConnector}, and {@link #exchangeStrategies}. + * @param exchangeFunction the exchange function to use + */ + Builder exchangeFunction(ExchangeFunction exchangeFunction); + /** * Clone this {@code WebClient.Builder} */ Builder clone(); /** - * Shortcut for pre-packaged customizations to WebTest builder. + * Apply the given {@code Consumer} to this builder instance. + *

This can be useful for applying pre-packaged customizations. * @param builderConsumer the consumer to apply */ Builder apply(Consumer builderConsumer); @@ -390,11 +379,9 @@ public interface WebClient { S cookie(String name, String value); /** - * Manipulate the request's cookies with the given consumer. The - * map provided to the consumer is "live", so that the consumer can be used to - * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, - * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other - * {@link MultiValueMap} methods. + * Manipulate the default cookies with the given consumer. The + * cookies provided to the consumer are "live", so that the consumer + * can be used to overwrite or remove existing values. * @param cookiesConsumer a function that consumes the cookies map * @return this builder */ @@ -425,11 +412,9 @@ public interface WebClient { S header(String headerName, String... headerValues); /** - * Manipulate the request's headers with the given consumer. The - * headers provided to the consumer are "live", so that the consumer can be used to - * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, - * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other - * {@link HttpHeaders} methods. + * Manipulate the default headers with the given consumer. The + * headers provided to the consumer are "live", so that the consumer + * can be used to overwrite or remove existing values. * @param headersConsumer a function that consumes the {@code HttpHeaders} * @return this builder */