Polish body methods

WebClient, WebTestClient, and ServerResponse
This commit is contained in:
Rossen Stoyanchev
2019-08-20 10:26:31 +03:00
parent b75674f5e9
commit 26dc93d0f6
4 changed files with 114 additions and 204 deletions

View File

@@ -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.
*
* <p>{@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.
* <p>Use one of the bindToXxx methods to create an instance. For example:
* <ul>
* <li>{@link #bindToController(Object...)}
* <li>{@link #bindToRouterFunction(RouterFunction)}
* <li>{@link #bindToApplicationContext(ApplicationContext)}
* <li>{@link #bindToServer()}
* <li>...
* </ul>
*
* <p>See the static {@code bindToXxx} entry points for creating an instance.
*
* <p><strong>Warning</strong>: {@code WebTestClient} is not usable yet in Kotlin due to a
* <a href="https://youtrack.jetbrains.com/issue/KT-5464">type inference issue</a> which is
* expected to be fixed as of Kotlin 1.3. You can watch
* <a href="https://jira.spring.io/browse/SPR-16057">SPR-16057</a> 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.
* <p><strong>Warning</strong>: {@code WebTestClient} is not usable yet in
* Kotlin due to a <a href="https://youtrack.jetbrains.com/issue/KT-5464">type inference issue</a>
* which is expected to be fixed as of Kotlin 1.3. You can watch
* <a href="https://github.com/spring-projects/spring-framework/issues/20606">gh-20606</a>
* 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.
* <p>This method is a convenient shortcut for:
* <pre class="code">
* .body(BodyInserters.fromObject(object))
* </pre>
* <p>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 <T> the type of the elements contained in the publisher
* @param <S> the type of the {@code Publisher}
* @return spec for decoding the response
* @return spec for further declaration of the request
*/
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> 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 <T> the type of the elements contained in the publisher
* @param <S> the type of the {@code Publisher}
* @return spec for decoding the response
* @return spec for further declaration of the request
* @since 5.2
*/
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, ParameterizedTypeReference<T> elementTypeRef);
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(
S publisher, ParameterizedTypeReference<T> 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<?, ? super ClientHttpRequest> inserter);
/**
* Set the body of the request to the given {@code Object} and perform the request.
* <p>This method is a convenient shortcut for:
* <pre class="code">
* .body(BodyInserters.fromObject(object))
* </pre>
* <p>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

View File

@@ -61,8 +61,6 @@ import org.springframework.web.util.UriBuilderFactory;
* <ul>
* <li>{@link RequestBodySpec#bodyValue(Object) bodyValue(Object)}
* <li>{@link RequestBodySpec#body(Publisher, Class) body(Publisher,Class)}
* <li>{@link RequestBodySpec#body(Object, Class) body(Object,Class)}
* <li>{@link RequestBodySpec#body(BodyInserter) body(BodyInserter)}
* </ul>
*
* @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:
* <p><pre class="code">
@@ -532,25 +530,20 @@ public interface WebClient {
* .retrieve()
* .bodyToMono(Void.class);
* </pre>
* <p>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
* <p>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:
* <p><pre>
@@ -564,7 +557,7 @@ public interface WebClient {
* .bodyToMono(Void.class);
* </pre>
* @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 <T> the type of the elements contained in the publisher
* @param <P> the type of the {@code Publisher}
* @return this builder
@@ -572,11 +565,10 @@ public interface WebClient {
<T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher, Class<T> 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 <T> the type of the elements contained in the publisher
* @param <P> the type of the {@code Publisher}
* @return this builder
@@ -585,36 +577,22 @@ public interface WebClient {
ParameterizedTypeReference<T> elementTypeRef);
/**
* A shortcut for {@link #body(BodyInserter)} with a
* {@linkplain BodyInserters#fromProducer inserter}.
* For example:
* <p><pre>
* Single&lt;Person&gt; personSingle = ... ;
*
* Mono&lt;Void&gt; result = client.post()
* .uri("/persons/{id}", id)
* .contentType(MediaType.APPLICATION_JSON)
* .body(personSingle, Person.class)
* .retrieve()
* .bodyToMono(Void.class);
* </pre>
* @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<?, ? super ClientHttpRequest> inserter);
/**
* A shortcut for {@link #body(BodyInserter)} with an
* Shortcut for {@link #body(BodyInserter)} with an
* {@linkplain BodyInserters#fromObject Object inserter}.
* For example:
* <p><pre class="code">
* Person person = ... ;
*
* Mono&lt;Void&gt; result = client.post()
* .uri("/persons/{id}", id)
* .contentType(MediaType.APPLICATION_JSON)
* .syncBody(person)
* .retrieve()
* .bodyToMono(Void.class);
* </pre>
* <p>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

View File

@@ -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<ServerResponse> build(
BiFunction<ServerWebExchange, ServerResponse.Context, Mono<Void>> 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<ServerResponse> 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 <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<T> 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 <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher,
ParameterizedTypeReference<T> 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 <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, ParameterizedTypeReference<T> typeRef) {
return initBuilder(publisher, BodyInserters.fromPublisher(publisher, typeRef));
}
@Override
public Mono<ServerResponse> 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<ServerResponse> body(Object producer, ParameterizedTypeReference<?> elementTypeRef) {
return new DefaultEntityResponseBuilder<>(producer,
BodyInserters.fromProducer(producer, elementTypeRef))
return initBuilder(producer, BodyInserters.fromProducer(producer, elementTypeRef));
}
private <T> Mono<ServerResponse> initBuilder(T entity, BodyInserter<T, ReactiveHttpOutputMessage> 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<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> 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

View File

@@ -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<ServerResponse> 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 <T> the type of the elements contained in the publisher
* @param <P> the type of the {@code Publisher}
* @return the built response
@@ -423,11 +422,11 @@ public interface ServerResponse {
<T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<T> 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 <T> the type of the elements contained in the publisher
* @param <P> the type of the {@code Publisher}
* @return the built response
@@ -436,26 +435,22 @@ public interface ServerResponse {
ParameterizedTypeReference<T> 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<ServerResponse> 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<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> 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