Request body improvements in WebClient, WebTestClient
This commit makes changes to WebClient and WebTestClient in oder to limit setting the body according to HTTP method and also to facilitate providing the request body as Object. Specifically, this commit: - Moves methods that operate on the request body to a RequestBodySpec in both WebClient and WebTestClient, and rename them to `body`. These methods now just *set* the body, without performing an exchange (which now requires an explicit exchange call). - Parameterizes UriSpec in both WebClient and WebTestClient, so that it returns either a RequestHeadersSpec or a RequestBodySpec. Issue: SPR-15394
This commit is contained in:
committed by
Rossen Stoyanchev
parent
cbd98d5247
commit
118f33aeda
@@ -34,6 +34,7 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
@@ -97,42 +98,45 @@ class DefaultWebTestClient implements WebTestClient {
|
||||
|
||||
|
||||
@Override
|
||||
public UriSpec get() {
|
||||
return toUriSpec(WebClient::get);
|
||||
public UriSpec<RequestHeadersSpec<?>> get() {
|
||||
return toUriSpec(wc -> wc.method(HttpMethod.GET));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriSpec head() {
|
||||
return toUriSpec(WebClient::head);
|
||||
public UriSpec<RequestHeadersSpec<?>> head() {
|
||||
return toUriSpec(wc -> wc.method(HttpMethod.HEAD));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriSpec post() {
|
||||
return toUriSpec(WebClient::post);
|
||||
public UriSpec<RequestBodySpec> post() {
|
||||
return toUriSpec(wc -> wc.method(HttpMethod.POST));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriSpec put() {
|
||||
return toUriSpec(WebClient::put);
|
||||
public UriSpec<RequestBodySpec> put() {
|
||||
return toUriSpec(wc -> wc.method(HttpMethod.PUT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriSpec patch() {
|
||||
return toUriSpec(WebClient::patch);
|
||||
public UriSpec<RequestBodySpec> patch() {
|
||||
return toUriSpec(wc -> wc.method(HttpMethod.PATCH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriSpec delete() {
|
||||
return toUriSpec(WebClient::delete);
|
||||
public UriSpec<RequestHeadersSpec<?>> delete() {
|
||||
return toUriSpec(wc -> wc.method(HttpMethod.DELETE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriSpec options() {
|
||||
return toUriSpec(WebClient::options);
|
||||
public UriSpec<RequestHeadersSpec<?>> options() {
|
||||
return toUriSpec(wc -> wc.method(HttpMethod.OPTIONS));
|
||||
}
|
||||
|
||||
private UriSpec toUriSpec(Function<WebClient, WebClient.UriSpec> function) {
|
||||
return new DefaultUriSpec(function.apply(this.webClient));
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S extends RequestHeadersSpec<?>> UriSpec<S> toUriSpec(
|
||||
Function<WebClient, WebClient.UriSpec<WebClient.RequestBodySpec>> function) {
|
||||
|
||||
return new DefaultUriSpec<>(function.apply(this.webClient));
|
||||
}
|
||||
|
||||
|
||||
@@ -156,123 +160,132 @@ class DefaultWebTestClient implements WebTestClient {
|
||||
}
|
||||
|
||||
|
||||
private class DefaultUriSpec implements UriSpec {
|
||||
@SuppressWarnings("unchecked")
|
||||
private class DefaultUriSpec<S extends RequestHeadersSpec<?>> implements UriSpec<S> {
|
||||
|
||||
private final WebClient.UriSpec uriSpec;
|
||||
private final WebClient.UriSpec<WebClient.RequestBodySpec> uriSpec;
|
||||
|
||||
|
||||
DefaultUriSpec(WebClient.UriSpec spec) {
|
||||
DefaultUriSpec(WebClient.UriSpec<WebClient.RequestBodySpec> spec) {
|
||||
this.uriSpec = spec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderSpec uri(URI uri) {
|
||||
return new DefaultHeaderSpec(this.uriSpec.uri(uri));
|
||||
public S uri(URI uri) {
|
||||
return (S) new DefaultRequestBodySpec(this.uriSpec.uri(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderSpec uri(String uriTemplate, Object... uriVariables) {
|
||||
return new DefaultHeaderSpec(this.uriSpec.uri(uriTemplate, uriVariables));
|
||||
public S uri(String uriTemplate, Object... uriVariables) {
|
||||
return (S) new DefaultRequestBodySpec(this.uriSpec.uri(uriTemplate, uriVariables));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderSpec uri(String uriTemplate, Map<String, ?> uriVariables) {
|
||||
return new DefaultHeaderSpec(this.uriSpec.uri(uriTemplate, uriVariables));
|
||||
public S uri(String uriTemplate, Map<String, ?> uriVariables) {
|
||||
return (S) new DefaultRequestBodySpec(this.uriSpec.uri(uriTemplate, uriVariables));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderSpec uri(Function<UriBuilder, URI> uriBuilder) {
|
||||
return new DefaultHeaderSpec(this.uriSpec.uri(uriBuilder));
|
||||
public S uri(Function<UriBuilder, URI> uriBuilder) {
|
||||
return (S) new DefaultRequestBodySpec(this.uriSpec.uri(uriBuilder));
|
||||
}
|
||||
}
|
||||
|
||||
private class DefaultHeaderSpec implements WebTestClient.HeaderSpec {
|
||||
private class DefaultRequestBodySpec implements RequestBodySpec {
|
||||
|
||||
private final WebClient.HeaderSpec headerSpec;
|
||||
private final WebClient.RequestBodySpec bodySpec;
|
||||
|
||||
private final String requestId;
|
||||
|
||||
|
||||
DefaultHeaderSpec(WebClient.HeaderSpec spec) {
|
||||
this.headerSpec = spec;
|
||||
DefaultRequestBodySpec(WebClient.RequestBodySpec spec) {
|
||||
this.bodySpec = spec;
|
||||
this.requestId = String.valueOf(requestIndex.incrementAndGet());
|
||||
this.headerSpec.header(WiretapConnector.REQUEST_ID_HEADER_NAME, this.requestId);
|
||||
this.bodySpec.header(WiretapConnector.REQUEST_ID_HEADER_NAME, this.requestId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec header(String headerName, String... headerValues) {
|
||||
this.headerSpec.header(headerName, headerValues);
|
||||
public RequestBodySpec header(String headerName, String... headerValues) {
|
||||
this.bodySpec.header(headerName, headerValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec headers(HttpHeaders headers) {
|
||||
this.headerSpec.headers(headers);
|
||||
public RequestBodySpec headers(HttpHeaders headers) {
|
||||
this.bodySpec.headers(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec accept(MediaType... acceptableMediaTypes) {
|
||||
this.headerSpec.accept(acceptableMediaTypes);
|
||||
public RequestBodySpec accept(MediaType... acceptableMediaTypes) {
|
||||
this.bodySpec.accept(acceptableMediaTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec acceptCharset(Charset... acceptableCharsets) {
|
||||
this.headerSpec.acceptCharset(acceptableCharsets);
|
||||
public RequestBodySpec acceptCharset(Charset... acceptableCharsets) {
|
||||
this.bodySpec.acceptCharset(acceptableCharsets);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec contentType(MediaType contentType) {
|
||||
this.headerSpec.contentType(contentType);
|
||||
public RequestBodySpec contentType(MediaType contentType) {
|
||||
this.bodySpec.contentType(contentType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec contentLength(long contentLength) {
|
||||
this.headerSpec.contentLength(contentLength);
|
||||
public RequestBodySpec contentLength(long contentLength) {
|
||||
this.bodySpec.contentLength(contentLength);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec cookie(String name, String value) {
|
||||
this.headerSpec.cookie(name, value);
|
||||
public RequestBodySpec cookie(String name, String value) {
|
||||
this.bodySpec.cookie(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec cookies(MultiValueMap<String, String> cookies) {
|
||||
this.headerSpec.cookies(cookies);
|
||||
public RequestBodySpec cookies(MultiValueMap<String, String> cookies) {
|
||||
this.bodySpec.cookies(cookies);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
|
||||
this.headerSpec.ifModifiedSince(ifModifiedSince);
|
||||
public RequestBodySpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
|
||||
this.bodySpec.ifModifiedSince(ifModifiedSince);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec ifNoneMatch(String... ifNoneMatches) {
|
||||
this.headerSpec.ifNoneMatch(ifNoneMatches);
|
||||
public RequestBodySpec ifNoneMatch(String... ifNoneMatches) {
|
||||
this.bodySpec.ifNoneMatch(ifNoneMatches);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseSpec exchange() {
|
||||
return toResponseSpec(this.headerSpec.exchange());
|
||||
return toResponseSpec(this.bodySpec.exchange());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ResponseSpec exchange(BodyInserter<T, ? super ClientHttpRequest> inserter) {
|
||||
return toResponseSpec(this.headerSpec.exchange(inserter));
|
||||
public <T> RequestHeadersSpec<?> body(BodyInserter<T, ? super ClientHttpRequest> inserter) {
|
||||
this.bodySpec.body(inserter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, S extends Publisher<T>> ResponseSpec exchange(S publisher, Class<T> elementClass) {
|
||||
return toResponseSpec(this.headerSpec.exchange(publisher, elementClass));
|
||||
public <T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass) {
|
||||
this.bodySpec.body(publisher, elementClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RequestHeadersSpec<?> body(T body) {
|
||||
this.bodySpec.body(body);
|
||||
return this;
|
||||
}
|
||||
|
||||
private DefaultResponseSpec toResponseSpec(Mono<ClientResponse> mono) {
|
||||
|
||||
@@ -78,43 +78,43 @@ public interface WebTestClient {
|
||||
* Prepare an HTTP GET request.
|
||||
* @return a spec for specifying the target URL
|
||||
*/
|
||||
UriSpec get();
|
||||
UriSpec<RequestHeadersSpec<?>> get();
|
||||
|
||||
/**
|
||||
* Prepare an HTTP HEAD request.
|
||||
* @return a spec for specifying the target URL
|
||||
*/
|
||||
UriSpec head();
|
||||
UriSpec<RequestHeadersSpec<?>> head();
|
||||
|
||||
/**
|
||||
* Prepare an HTTP POST request.
|
||||
* @return a spec for specifying the target URL
|
||||
*/
|
||||
UriSpec post();
|
||||
UriSpec<RequestBodySpec> post();
|
||||
|
||||
/**
|
||||
* Prepare an HTTP PUT request.
|
||||
* @return a spec for specifying the target URL
|
||||
*/
|
||||
UriSpec put();
|
||||
UriSpec<RequestBodySpec> put();
|
||||
|
||||
/**
|
||||
* Prepare an HTTP PATCH request.
|
||||
* @return a spec for specifying the target URL
|
||||
*/
|
||||
UriSpec patch();
|
||||
UriSpec<RequestBodySpec> patch();
|
||||
|
||||
/**
|
||||
* Prepare an HTTP DELETE request.
|
||||
* @return a spec for specifying the target URL
|
||||
*/
|
||||
UriSpec delete();
|
||||
UriSpec<RequestHeadersSpec<?>> delete();
|
||||
|
||||
/**
|
||||
* Prepare an HTTP OPTIONS request.
|
||||
* @return a spec for specifying the target URL
|
||||
*/
|
||||
UriSpec options();
|
||||
UriSpec<RequestHeadersSpec<?>> options();
|
||||
|
||||
|
||||
/**
|
||||
@@ -327,13 +327,13 @@ public interface WebTestClient {
|
||||
/**
|
||||
* Specification for providing the URI of a request.
|
||||
*/
|
||||
interface UriSpec {
|
||||
interface UriSpec<S extends RequestHeadersSpec<?>> {
|
||||
|
||||
/**
|
||||
* Specify the URI using an absolute, fully constructed {@link URI}.
|
||||
* @return spec to add headers or perform the exchange
|
||||
*/
|
||||
HeaderSpec uri(URI uri);
|
||||
S uri(URI uri);
|
||||
|
||||
/**
|
||||
* Specify the URI for the request using a URI template and URI variables.
|
||||
@@ -341,7 +341,7 @@ public interface WebTestClient {
|
||||
* with a base URI) it will be used to expand the URI template.
|
||||
* @return spec to add headers or perform the exchange
|
||||
*/
|
||||
HeaderSpec uri(String uri, Object... uriVariables);
|
||||
S uri(String uri, Object... uriVariables);
|
||||
|
||||
/**
|
||||
* Specify the URI for the request using a URI template and URI variables.
|
||||
@@ -349,21 +349,21 @@ public interface WebTestClient {
|
||||
* with a base URI) it will be used to expand the URI template.
|
||||
* @return spec to add headers or perform the exchange
|
||||
*/
|
||||
HeaderSpec uri(String uri, Map<String, ?> uriVariables);
|
||||
S uri(String uri, Map<String, ?> uriVariables);
|
||||
|
||||
/**
|
||||
* Build the URI for the request with a {@link UriBuilder} obtained
|
||||
* through the {@link UriBuilderFactory} configured for this client.
|
||||
* @return spec to add headers or perform the exchange
|
||||
*/
|
||||
HeaderSpec uri(Function<UriBuilder, URI> uriFunction);
|
||||
S uri(Function<UriBuilder, URI> uriFunction);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Specification for adding request headers and performing an exchange.
|
||||
*/
|
||||
interface HeaderSpec {
|
||||
interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
|
||||
|
||||
/**
|
||||
* Set the list of acceptable {@linkplain MediaType media types}, as
|
||||
@@ -371,7 +371,7 @@ public interface WebTestClient {
|
||||
* @param acceptableMediaTypes the acceptable media types
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec accept(MediaType... acceptableMediaTypes);
|
||||
S accept(MediaType... acceptableMediaTypes);
|
||||
|
||||
/**
|
||||
* Set the list of acceptable {@linkplain Charset charsets}, as specified
|
||||
@@ -379,25 +379,7 @@ public interface WebTestClient {
|
||||
* @param acceptableCharsets the acceptable charsets
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec acceptCharset(Charset... acceptableCharsets);
|
||||
|
||||
/**
|
||||
* Set the length of the body in bytes, as specified by the
|
||||
* {@code Content-Length} header.
|
||||
* @param contentLength the content length
|
||||
* @return the same instance
|
||||
* @see HttpHeaders#setContentLength(long)
|
||||
*/
|
||||
HeaderSpec contentLength(long contentLength);
|
||||
|
||||
/**
|
||||
* Set the {@linkplain MediaType media type} of the body, as specified
|
||||
* by the {@code Content-Type} header.
|
||||
* @param contentType the content type
|
||||
* @return the same instance
|
||||
* @see HttpHeaders#setContentType(MediaType)
|
||||
*/
|
||||
HeaderSpec contentType(MediaType contentType);
|
||||
S acceptCharset(Charset... acceptableCharsets);
|
||||
|
||||
/**
|
||||
* Add a cookie with the given name and value.
|
||||
@@ -405,7 +387,7 @@ public interface WebTestClient {
|
||||
* @param value the cookie value
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec cookie(String name, String value);
|
||||
S cookie(String name, String value);
|
||||
|
||||
/**
|
||||
* Copy the given cookies into the entity's cookies map.
|
||||
@@ -413,7 +395,7 @@ public interface WebTestClient {
|
||||
* @param cookies the existing cookies to copy from
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec cookies(MultiValueMap<String, String> cookies);
|
||||
S cookies(MultiValueMap<String, String> cookies);
|
||||
|
||||
/**
|
||||
* Set the value of the {@code If-Modified-Since} header.
|
||||
@@ -422,14 +404,14 @@ public interface WebTestClient {
|
||||
* @param ifModifiedSince the new value of the header
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince);
|
||||
S ifModifiedSince(ZonedDateTime ifModifiedSince);
|
||||
|
||||
/**
|
||||
* Set the values of the {@code If-None-Match} header.
|
||||
* @param ifNoneMatches the new value of the header
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec ifNoneMatch(String... ifNoneMatches);
|
||||
S ifNoneMatch(String... ifNoneMatches);
|
||||
|
||||
/**
|
||||
* Add the given, single header value under the given name.
|
||||
@@ -437,14 +419,14 @@ public interface WebTestClient {
|
||||
* @param headerValues the header value(s)
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec header(String headerName, String... headerValues);
|
||||
S header(String headerName, String... headerValues);
|
||||
|
||||
/**
|
||||
* Copy the given headers into the entity's headers map.
|
||||
* @param headers the existing headers to copy from
|
||||
* @return the same instance
|
||||
*/
|
||||
HeaderSpec headers(HttpHeaders headers);
|
||||
S headers(HttpHeaders headers);
|
||||
|
||||
/**
|
||||
* Perform the exchange without a request body.
|
||||
@@ -452,26 +434,55 @@ public interface WebTestClient {
|
||||
*/
|
||||
ResponseSpec exchange();
|
||||
|
||||
}
|
||||
|
||||
interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
|
||||
/**
|
||||
* Perform the exchange with the body for the request populated using
|
||||
* a {@link BodyInserter}.
|
||||
* Set the length of the body in bytes, as specified by the
|
||||
* {@code Content-Length} header.
|
||||
* @param contentLength the content length
|
||||
* @return the same instance
|
||||
* @see HttpHeaders#setContentLength(long)
|
||||
*/
|
||||
RequestBodySpec contentLength(long contentLength);
|
||||
|
||||
/**
|
||||
* Set the {@linkplain MediaType media type} of the body, as specified
|
||||
* by the {@code Content-Type} header.
|
||||
* @param contentType the content type
|
||||
* @return the same instance
|
||||
* @see HttpHeaders#setContentType(MediaType)
|
||||
*/
|
||||
RequestBodySpec contentType(MediaType contentType);
|
||||
|
||||
/**
|
||||
* Set the body of the request to the given {@code BodyInserter}.
|
||||
* @param inserter the inserter
|
||||
* @param <T> the body type, or the the element type (for a stream)
|
||||
* @return spec for decoding the response
|
||||
* @see org.springframework.web.reactive.function.BodyInserters
|
||||
*/
|
||||
<T> ResponseSpec exchange(BodyInserter<T, ? super ClientHttpRequest> inserter);
|
||||
<T> RequestHeadersSpec<?> body(BodyInserter<T, ? super ClientHttpRequest> inserter);
|
||||
|
||||
/**
|
||||
* Perform the exchange and use the given {@code Publisher} for the
|
||||
* request body.
|
||||
* Set the body of the request to the given {@code Publisher}.
|
||||
* @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
|
||||
*/
|
||||
<T, S extends Publisher<T>> ResponseSpec exchange(S publisher, Class<T> elementClass);
|
||||
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass);
|
||||
|
||||
/**
|
||||
* Set the body of the request to the given {@code Object} and
|
||||
* perform the request.
|
||||
* @param body the {@code Object} to write to the request
|
||||
* @param <T> the type contained in the body
|
||||
* @return a {@code Mono} with the response
|
||||
*/
|
||||
<T> RequestHeadersSpec<?> body(T body);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user