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 c88dfce3d8..21d370a7b6 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 @@ -47,6 +47,7 @@ import org.springframework.web.reactive.config.PathMatchConfigurer; import org.springframework.web.reactive.config.ViewResolverRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; @@ -60,22 +61,27 @@ import org.springframework.web.util.UriBuilder; import org.springframework.web.util.UriBuilderFactory; /** - * Non-blocking, reactive client for testing web servers. It uses the reactive - * {@link WebClient} internally to perform requests and provides a fluent API - * to verify responses. + * Client for testing web servers that uses {@link WebClient} internally to + * perform requests while also providing a fluent API to verify responses. + * This client can connect to any server over HTTP, or to a WebFlux application + * via mock request and response objects. * - *

{@code WebTestClient} can connect to any server over an HTTP connection. - * It can also bind directly to WebFlux applications using mock request and - * response objects, without the need for an HTTP server. + *

Use one of the bindToXxx methods to create an instance. For example: + *

* - *

See the static {@code bindToXxx} entry points for creating an instance. - * - *

Warning: {@code WebTestClient} is not usable yet in Kotlin due to a - * type inference issue which is - * expected to be fixed as of Kotlin 1.3. You can watch - * SPR-16057 for up-to-date information. - * Meanwhile, the proposed alternative is to use directly {@link WebClient} with its Reactor - * and Spring Kotlin extensions to perform integration tests on an embedded WebFlux server. + *

Warning: {@code WebTestClient} is not usable yet in + * Kotlin due to a type inference issue + * which is expected to be fixed as of Kotlin 1.3. You can watch + * gh-20606 + * for up-to-date information. Meanwhile, the proposed alternative is to use + * directly {@link WebClient} with its Reactor and Spring Kotlin extensions to + * perform integration tests on an embedded WebFlux server. * * @author Rossen Stoyanchev * @since 5.0 @@ -626,93 +632,81 @@ public interface WebTestClient { RequestBodySpec contentType(MediaType contentType); /** - * Set the body of the request to the given {@code Object} and perform the request. - *

This method is a convenient shortcut for: - *

-		 * .body(BodyInserters.fromObject(object))
-		 * 
- *

The body can be a - * {@link org.springframework.util.MultiValueMap MultiValueMap} to create - * a multipart request. The values in the {@code MultiValueMap} can be - * any Object representing the body of the part, or an - * {@link org.springframework.http.HttpEntity HttpEntity} representing a - * part with body and headers. The {@code MultiValueMap} can be built - * conveniently using - * @param body the {@code Object} to write to the request - * @return spec for decoding the response + * Set the body to the given {@code Object} value. This method invokes the + * {@link WebClient.RequestBodySpec#bodyValue(Object) bodyValue} method + * on the underlying {@code WebClient}. + * @param body the value to write to the request body + * @return spec for further declaration of the request * @since 5.2 */ RequestHeadersSpec bodyValue(Object body); /** - * Set the body of the request to the given asynchronous {@code Publisher}. + * Set the body from the given {@code Publisher}. Shortcut for + * {@link #body(BodyInserter)} with a + * {@linkplain BodyInserters#fromPublisher Publisher inserter}. * @param publisher the request body data * @param elementClass the class of elements contained in the publisher * @param the type of the elements contained in the publisher * @param the type of the {@code Publisher} - * @return spec for decoding the response + * @return spec for further declaration of the request */ > RequestHeadersSpec body(S publisher, Class elementClass); /** - * Set the body of the request to the given asynchronous {@code Publisher}. + * Variant of {@link #body(Publisher, Class)} that allows providing + * element type information with generics. * @param publisher the request body data * @param elementTypeRef the type reference of elements contained in the publisher * @param the type of the elements contained in the publisher * @param the type of the {@code Publisher} - * @return spec for decoding the response + * @return spec for further declaration of the request * @since 5.2 */ - > RequestHeadersSpec body(S publisher, ParameterizedTypeReference elementTypeRef); + > RequestHeadersSpec body( + S publisher, ParameterizedTypeReference elementTypeRef); /** - * Set the body of the request to the given producer. + * Set the body from the given producer. This method invokes the + * {@link WebClient.RequestBodySpec#body(Object, Class)} method on the + * underlying {@code WebClient}. * @param producer the producer to write to the request. This must be a * {@link Publisher} or another producer adaptable to a * {@code Publisher} via {@link ReactiveAdapterRegistry} * @param elementClass the class of elements contained in the producer - * @return spec for decoding the response + * @return spec for further declaration of the request * @since 5.2 */ RequestHeadersSpec body(Object producer, Class elementClass); /** - * Set the body of the request to the given producer. + * Set the body from the given producer. This method invokes the + * {@link WebClient.RequestBodySpec#body(Object, ParameterizedTypeReference)} + * method on the underlying {@code WebClient}. * @param producer the producer to write to the request. This must be a * {@link Publisher} or another producer adaptable to a * {@code Publisher} via {@link ReactiveAdapterRegistry} * @param elementTypeRef the type reference of elements contained in the producer - * @return spec for decoding the response + * @return spec for further declaration of the request * @since 5.2 */ RequestHeadersSpec body(Object producer, ParameterizedTypeReference elementTypeRef); /** * Set the body of the request to the given {@code BodyInserter}. - * @param inserter the inserter - * @return spec for decoding the response + * This method invokes the + * {@link WebClient.RequestBodySpec#body(BodyInserter)} method on the + * underlying {@code WebClient}. + * @param inserter the body inserter to use + * @return spec for further declaration of the request * @see org.springframework.web.reactive.function.BodyInserters */ RequestHeadersSpec body(BodyInserter inserter); /** - * Set the body of the request to the given {@code Object} and perform the request. - *

This method is a convenient shortcut for: - *

-		 * .body(BodyInserters.fromObject(object))
-		 * 
- *

The body can be a - * {@link org.springframework.util.MultiValueMap MultiValueMap} to create - * a multipart request. The values in the {@code MultiValueMap} can be - * any Object representing the body of the part, or an - * {@link org.springframework.http.HttpEntity HttpEntity} representing a - * part with body and headers. The {@code MultiValueMap} can be built - * conveniently using - * @param body the {@code Object} to write to the request - * @return spec for decoding the response - * @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an - * instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()}, - * for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used. + * Shortcut for {@link #body(BodyInserter)} with an + * {@linkplain BodyInserters#fromObject Object inserter}. + * As of 5.2 this method delegates to {@link #bodyValue(Object)}. * @deprecated as of Spring Framework 5.2 in favor of {@link #bodyValue(Object)} */ @Deprecated 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 6b64ab84c7..0401b4be81 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 @@ -61,8 +61,6 @@ import org.springframework.web.util.UriBuilderFactory; *

* * @author Rossen Stoyanchev @@ -519,7 +517,7 @@ public interface WebClient { RequestBodySpec contentType(MediaType contentType); /** - * A shortcut for {@link #body(BodyInserter)} with an + * Shortcut for {@link #body(BodyInserter)} with an * {@linkplain BodyInserters#fromObject Object inserter}. * For example: *

@@ -532,25 +530,20 @@ public interface WebClient {
 		 *     .retrieve()
 		 *     .bodyToMono(Void.class);
 		 * 
- *

For multipart requests, provide a - * {@link org.springframework.util.MultiValueMap MultiValueMap}. The - * values in the {@code MultiValueMap} can be any Object representing - * the body of the part, or an - * {@link org.springframework.http.HttpEntity HttpEntity} representing - * a part with body and headers. The {@code MultiValueMap} can be built + *

For multipart requests consider providing + * {@link org.springframework.util.MultiValueMap MultiValueMap} prepared * with {@link org.springframework.http.client.MultipartBodyBuilder * MultipartBodyBuilder}. - * @param body the {@code Object} to write to the request + * @param body the value to write to the request body * @return this builder - * @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an - * instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()}, - * for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used. + * @throws IllegalArgumentException if {@code body} is a + * {@link Publisher} or producer known to {@link ReactiveAdapterRegistry} * @since 5.2 */ RequestHeadersSpec bodyValue(Object body); /** - * A shortcut for {@link #body(BodyInserter)} with a + * Shortcut for {@link #body(BodyInserter)} with a * {@linkplain BodyInserters#fromPublisher Publisher inserter}. * For example: *

@@ -564,7 +557,7 @@ public interface WebClient {
 		 *     .bodyToMono(Void.class);
 		 * 
* @param publisher the {@code Publisher} to write to the request - * @param elementClass the class of elements contained in the publisher + * @param elementClass the type of elements published * @param the type of the elements contained in the publisher * @param

the type of the {@code Publisher} * @return this builder @@ -572,11 +565,10 @@ public interface WebClient { > RequestHeadersSpec body(P publisher, Class elementClass); /** - * A variant of {@link #body(Publisher, Class)} that allows providing - * element type information that includes generics via a - * {@link ParameterizedTypeReference}. + * Variant of {@link #body(Publisher, Class)} that allows providing + * element type information with generics. * @param publisher the {@code Publisher} to write to the request - * @param elementTypeRef the type reference of elements contained in the publisher + * @param elementTypeRef the type of elements published * @param the type of the elements contained in the publisher * @param

the type of the {@code Publisher} * @return this builder @@ -585,36 +577,22 @@ public interface WebClient { ParameterizedTypeReference elementTypeRef); /** - * A shortcut for {@link #body(BodyInserter)} with a - * {@linkplain BodyInserters#fromProducer inserter}. - * For example: - *

-		 * Single<Person> personSingle = ... ;
-		 *
-		 * Mono<Void> result = client.post()
-		 *     .uri("/persons/{id}", id)
-		 *     .contentType(MediaType.APPLICATION_JSON)
-		 *     .body(personSingle, Person.class)
-		 *     .retrieve()
-		 *     .bodyToMono(Void.class);
-		 * 
- * @param producer the producer to write to the request. This must be a - * {@link Publisher} or another producer adaptable to a - * {@code Publisher} via {@link ReactiveAdapterRegistry} - * @param elementClass the class of elements contained in the producer + * Variant of {@link #body(Publisher, Class)} that allows using any + * producer that can be resolved to {@link Publisher} via + * {@link ReactiveAdapterRegistry}. + * @param producer the producer to write to the request + * @param elementClass the type of elements produced * @return this builder * @since 5.2 */ RequestHeadersSpec body(Object producer, Class elementClass); /** - * A variant of {@link #body(Object, Class)} that allows providing - * element type information that includes generics via a - * {@link ParameterizedTypeReference}. - * @param producer the producer to write to the request. This must be a - * {@link Publisher} or another producer adaptable to a - * {@code Publisher} via {@link ReactiveAdapterRegistry} - * @param elementTypeRef the type reference of elements contained in the producer + * Variant of {@link #body(Publisher, ParameterizedTypeReference)} that + * allows using any producer that can be resolved to {@link Publisher} + * via {@link ReactiveAdapterRegistry}. + * @param producer the producer to write to the request + * @param elementTypeRef the type of elements produced * @return this builder * @since 5.2 */ @@ -622,8 +600,7 @@ public interface WebClient { /** * Set the body of the request using the given body inserter. - * {@link BodyInserters} provides access to built-in implementations of - * {@link BodyInserter}. + * See {@link BodyInserters} for built-in {@link BodyInserter} implementations. * @param inserter the body inserter to use for the request body * @return this builder * @see org.springframework.web.reactive.function.BodyInserters @@ -631,32 +608,9 @@ public interface WebClient { RequestHeadersSpec body(BodyInserter inserter); /** - * A shortcut for {@link #body(BodyInserter)} with an + * Shortcut for {@link #body(BodyInserter)} with an * {@linkplain BodyInserters#fromObject Object inserter}. - * For example: - *

-		 * Person person = ... ;
-		 *
-		 * Mono<Void> result = client.post()
-		 *     .uri("/persons/{id}", id)
-		 *     .contentType(MediaType.APPLICATION_JSON)
-		 *     .syncBody(person)
-		 *     .retrieve()
-		 *     .bodyToMono(Void.class);
-		 * 
- *

For multipart requests, provide a - * {@link org.springframework.util.MultiValueMap MultiValueMap}. The - * values in the {@code MultiValueMap} can be any Object representing - * the body of the part, or an - * {@link org.springframework.http.HttpEntity HttpEntity} representing - * a part with body and headers. The {@code MultiValueMap} can be built - * with {@link org.springframework.http.client.MultipartBodyBuilder - * MultipartBodyBuilder}. - * @param body the {@code Object} to write to the request - * @return this builder - * @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an - * instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()}, - * for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used. + * As of 5.2 this method delegates to {@link #bodyValue(Object)}. * @deprecated as of Spring Framework 5.2 in favor of {@link #bodyValue(Object)} */ @Deprecated diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java index 39f57133cd..fd93bf3137 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java @@ -41,6 +41,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.http.ResponseCookie; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.server.reactive.AbstractServerHttpResponse; @@ -218,63 +219,37 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { public Mono build( BiFunction> writeFunction) { - return Mono.just( - new WriterFunctionResponse(this.statusCode, this.headers, this.cookies, writeFunction)); + return Mono.just(new WriterFunctionResponse( + this.statusCode, this.headers, this.cookies, writeFunction)); } @Override public Mono bodyValue(Object body) { - return new DefaultEntityResponseBuilder<>(body, - BodyInserters.fromObject(body)) - .status(this.statusCode) - .headers(this.headers) - .cookies(cookies -> cookies.addAll(this.cookies)) - .hints(hints -> hints.putAll(this.hints)) - .build() - .map(entityResponse -> entityResponse); + return initBuilder(body, BodyInserters.fromObject(body)); } @Override public > Mono body(P publisher, Class elementClass) { - return new DefaultEntityResponseBuilder<>(publisher, - BodyInserters.fromPublisher(publisher, elementClass)) - .status(this.statusCode) - .headers(this.headers) - .cookies(cookies -> cookies.addAll(this.cookies)) - .hints(hints -> hints.putAll(this.hints)) - .build() - .map(entityResponse -> entityResponse); + return initBuilder(publisher, BodyInserters.fromPublisher(publisher, elementClass)); } @Override - public > Mono body(P publisher, - ParameterizedTypeReference elementTypeRef) { - return new DefaultEntityResponseBuilder<>(publisher, - BodyInserters.fromPublisher(publisher, elementTypeRef)) - .status(this.statusCode) - .headers(this.headers) - .cookies(cookies -> cookies.addAll(this.cookies)) - .hints(hints -> hints.putAll(this.hints)) - .build() - .map(entityResponse -> entityResponse); + public > Mono body(P publisher, ParameterizedTypeReference typeRef) { + return initBuilder(publisher, BodyInserters.fromPublisher(publisher, typeRef)); } @Override public Mono body(Object producer, Class elementClass) { - return new DefaultEntityResponseBuilder<>(producer, - BodyInserters.fromProducer(producer, elementClass)) - .status(this.statusCode) - .headers(this.headers) - .cookies(cookies -> cookies.addAll(this.cookies)) - .hints(hints -> hints.putAll(this.hints)) - .build() - .map(entityResponse -> entityResponse); + return initBuilder(producer, BodyInserters.fromProducer(producer, elementClass)); } @Override public Mono body(Object producer, ParameterizedTypeReference elementTypeRef) { - return new DefaultEntityResponseBuilder<>(producer, - BodyInserters.fromProducer(producer, elementTypeRef)) + return initBuilder(producer, BodyInserters.fromProducer(producer, elementTypeRef)); + } + + private Mono initBuilder(T entity, BodyInserter inserter) { + return new DefaultEntityResponseBuilder<>(entity, inserter) .status(this.statusCode) .headers(this.headers) .cookies(cookies -> cookies.addAll(this.cookies)) @@ -285,8 +260,8 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { @Override public Mono body(BodyInserter inserter) { - return Mono.just( - new BodyInserterResponse<>(this.statusCode, this.headers, this.cookies, inserter, this.hints)); + return Mono.just(new BodyInserterResponse<>( + this.statusCode, this.headers, this.cookies, inserter, this.hints)); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java index cdf620c426..242d3a2421 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java @@ -399,23 +399,22 @@ public interface ServerResponse { /** * Set the body of the response to the given {@code Object} and return it. - * This convenience method combines {@link #body(BodyInserter)} and - * {@link BodyInserters#fromObject(Object)}. + * This is a shortcut for using a {@link #body(BodyInserter)} with an + * {@linkplain BodyInserters#fromObject Object inserter}. * @param body the body of the response * @return the built response - * @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an - * instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()}, - * for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used. + * @throws IllegalArgumentException if {@code body} is a + * {@link Publisher} or producer known to {@link ReactiveAdapterRegistry} * @since 5.2 */ Mono bodyValue(Object body); /** - * Set the body of the response to the given asynchronous {@code Publisher} and return it. - * This convenience method combines {@link #body(BodyInserter)} and - * {@link BodyInserters#fromPublisher(Publisher, Class)}. + * Set the body from the given {@code Publisher}. Shortcut for + * {@link #body(BodyInserter)} with a + * {@linkplain BodyInserters#fromPublisher Publisher inserter}. * @param publisher the {@code Publisher} to write to the response - * @param elementClass the class of elements contained in the publisher + * @param elementClass the type of elements published * @param the type of the elements contained in the publisher * @param

the type of the {@code Publisher} * @return the built response @@ -423,11 +422,11 @@ public interface ServerResponse { > Mono body(P publisher, Class elementClass); /** - * Set the body of the response to the given asynchronous {@code Publisher} and return it. - * This convenience method combines {@link #body(BodyInserter)} and - * {@link BodyInserters#fromPublisher(Publisher, ParameterizedTypeReference)}. - * @param publisher the {@code Publisher} to write to the response - * @param elementTypeRef a type reference describing the elements contained in the publisher + * Variant of {@link #body(Publisher, Class)} that allows using any + * producer that can be resolved to {@link Publisher} via + * {@link ReactiveAdapterRegistry}. + * @param publisher the {@code Publisher} to use to write the response + * @param elementTypeRef the type of elements produced * @param the type of the elements contained in the publisher * @param

the type of the {@code Publisher} * @return the built response @@ -436,26 +435,22 @@ public interface ServerResponse { ParameterizedTypeReference elementTypeRef); /** - * Set the body of the response to the given asynchronous {@code Publisher} and return it. - * This convenience method combines {@link #body(BodyInserter)} and - * {@link BodyInserters#fromProducer(Object, Class)}. - * @param producer the producer to write to the response. This must be a - * {@link Publisher} or another producer adaptable to a - * {@code Publisher} via {@link ReactiveAdapterRegistry} - * @param elementClass the class of elements contained in the producer + * Variant of {@link #body(Publisher, Class)} that allows using any + * producer that can be resolved to {@link Publisher} via + * {@link ReactiveAdapterRegistry}. + * @param producer the producer to write to the request + * @param elementClass the type of elements produced * @return the built response * @since 5.2 */ Mono body(Object producer, Class elementClass); /** - * Set the body of the response to the given asynchronous {@code Publisher} and return it. - * This convenience method combines {@link #body(BodyInserter)} and - * {@link BodyInserters#fromProducer(Object, ParameterizedTypeReference)}. - * @param producer the producer to write to the response. This must be a - * {@link Publisher} or another producer adaptable to a - * {@code Publisher} via {@link ReactiveAdapterRegistry} - * @param elementTypeRef a type reference describing the elements contained in the producer + * Variant of {@link #body(Publisher, ParameterizedTypeReference)} that + * allows using any producer that can be resolved to {@link Publisher} + * via {@link ReactiveAdapterRegistry}. + * @param producer the producer to write to the response + * @param elementTypeRef the type of elements produced * @return the built response * @since 5.2 */ @@ -469,16 +464,8 @@ public interface ServerResponse { Mono body(BodyInserter inserter); /** - * Set the body of the response to the given {@code Object} and return it. - * This convenience method combines {@link #body(BodyInserter)} and - * {@link BodyInserters#fromObject(Object)}. - * @param body the body of the response - * @return the built response - * @throws IllegalArgumentException if {@code body} is a {@link Publisher}, for which - * {@link #body(Publisher, Class)} should be used. - * @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an - * instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()}, - * for which {@link #body(Publisher, Class)} or {@link #body(Object, Class)} should be used. + * Set the response body to the given {@code Object} and return it. + * As of 5.2 this method delegates to {@link #bodyValue(Object)}. * @deprecated as of Spring Framework 5.2 in favor of {@link #bodyValue(Object)} */ @Deprecated