Refactor HttpRequestSpec to HttpRequestValues

HttpRequestValues is immutable and exposes a builder.

See gh-28386
This commit is contained in:
rstoyanchev 2022-04-27 09:25:23 +01:00
parent 564f8ba7a0
commit d7ab5b4132
12 changed files with 484 additions and 348 deletions

View File

@ -33,18 +33,18 @@ import org.springframework.http.ResponseEntity;
*/ */
public interface HttpClientAdapter { public interface HttpClientAdapter {
Mono<Void> requestToVoid(HttpRequestSpec spec); Mono<Void> requestToVoid(HttpRequestValues requestValues);
Mono<HttpHeaders> requestToHeaders(HttpRequestSpec spec); Mono<HttpHeaders> requestToHeaders(HttpRequestValues requestValues);
<T> Mono<T> requestToBody(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType); <T> Mono<T> requestToBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
<T> Flux<T> requestToBodyFlux(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType); <T> Flux<T> requestToBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestSpec spec); Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestValues requestValues);
<T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType); <T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
<T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType); <T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType);
} }

View File

@ -38,7 +38,7 @@ public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
@Override @Override
public void resolve( public void resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestSpec requestSpec) { @Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (argument == null) { if (argument == null) {
return; return;
@ -47,7 +47,7 @@ public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Resolved HTTP method to: " + httpMethod.name()); logger.trace("Resolved HTTP method to: " + httpMethod.name());
} }
requestSpec.setHttpMethod(httpMethod); requestValues.setHttpMethod(httpMethod);
} }
} }

View File

@ -1,196 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.service.invoker;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.reactivestreams.Publisher;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Container for HTTP request values extracted from an
* {@link org.springframework.web.service.annotation.HttpExchange @HttpExchange}-annotated
* method and argument values passed to it. This is then given to
* {@link HttpClientAdapter} to adapt to the underlying HTTP client.
*
* @author Rossen Stoyanchev
* @since 6.0
*/
public class HttpRequestSpec {
private static final MultiValueMap<String, String> EMPTY_COOKIES_MAP =
CollectionUtils.toMultiValueMap(Collections.emptyMap());
@Nullable
private URI uri;
@Nullable
private String uriTemplate;
@Nullable
private Map<String, String> uriVariables;
@Nullable
private List<String> uriVariablesList;
@Nullable
private HttpMethod httpMethod;
@Nullable
private HttpHeaders headers;
@Nullable
private MultiValueMap<String, String> cookies;
@Nullable
private Object bodyValue;
@Nullable
private Publisher<?> bodyPublisher;
@Nullable
private ParameterizedTypeReference<?> bodyPublisherElementType;
private boolean complete;
public HttpRequestSpec() {
}
public void setUri(URI uri) {
checkComplete();
this.uri = uri;
}
@Nullable
public URI getUri() {
return this.uri;
}
public void setUriTemplate(String uriTemplate) {
checkComplete();
this.uriTemplate = uriTemplate;
}
@Nullable
public String getUriTemplate() {
return this.uriTemplate;
}
public Map<String, String> getUriVariables() {
this.uriVariables = (this.uriVariables != null ? this.uriVariables : new LinkedHashMap<>());
return this.uriVariables;
}
public List<String> getUriVariableValues() {
this.uriVariablesList = (this.uriVariablesList != null ? this.uriVariablesList : new ArrayList<>());
return this.uriVariablesList;
}
public void setHttpMethod(HttpMethod httpMethod) {
checkComplete();
this.httpMethod = httpMethod;
}
@Nullable
public HttpMethod getHttpMethod() {
return this.httpMethod;
}
public HttpMethod getHttpMethodRequired() {
Assert.notNull(this.httpMethod, "No HttpMethod");
return this.httpMethod;
}
public HttpHeaders getHeaders() {
this.headers = (this.headers != null ? this.headers : new HttpHeaders());
return this.headers;
}
public MultiValueMap<String, String> getCookies() {
this.cookies = (this.cookies != null ? this.cookies : new LinkedMultiValueMap<>());
return this.cookies;
}
public void setBodyValue(Object bodyValue) {
checkComplete();
this.bodyValue = bodyValue;
}
@Nullable
public Object getBodyValue() {
return this.bodyValue;
}
public <T, P extends Publisher<T>> void setBodyPublisher(Publisher<P> bodyPublisher, MethodParameter parameter) {
checkComplete();
// Adapt to Mono/Flux and nest MethodParameter for element type
this.bodyPublisher = bodyPublisher;
this.bodyPublisherElementType = ParameterizedTypeReference.forType(parameter.nested().getGenericParameterType());
}
@Nullable
public Publisher<?> getBodyPublisher() {
return this.bodyPublisher;
}
public ParameterizedTypeReference<?> getBodyPublisherElementType() {
Assert.state(this.bodyPublisherElementType != null, "No body Publisher");
return this.bodyPublisherElementType;
}
private void checkComplete() {
Assert.isTrue(!this.complete, "setComplete already called");
}
void setComplete() {
this.complete = true;
this.uriVariables = (this.uriVariables != null ?
Collections.unmodifiableMap(this.uriVariables) : Collections.emptyMap());
this.uriVariablesList = (this.uriVariablesList != null ?
Collections.unmodifiableList(this.uriVariablesList) : Collections.emptyList());
this.headers = (this.headers != null ?
HttpHeaders.readOnlyHttpHeaders(this.headers) : HttpHeaders.EMPTY);
this.cookies = (this.cookies != null ?
CollectionUtils.unmodifiableMultiValueMap(this.cookies) : EMPTY_COOKIES_MAP);
}
}

View File

@ -0,0 +1,337 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.service.invoker;
import java.net.URI;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.reactivestreams.Publisher;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Container for HTTP request values extracted from an
* {@link org.springframework.web.service.annotation.HttpExchange @HttpExchange}-annotated
* method and argument values passed to it. This is then given to
* {@link HttpClientAdapter} to adapt to the underlying HTTP client.
*
* @author Rossen Stoyanchev
* @since 6.0
*/
public final class HttpRequestValues {
private static final MultiValueMap<String, String> EMPTY_COOKIES_MAP =
CollectionUtils.toMultiValueMap(Collections.emptyMap());
private final HttpMethod httpMethod;
@Nullable
private final URI uri;
@Nullable
private final String uriTemplate;
private final Map<String, String> uriVariables;
private final HttpHeaders headers;
private final MultiValueMap<String, String> cookies;
@Nullable
private final Object bodyValue;
@Nullable
private final Publisher<?> body;
@Nullable
private final ParameterizedTypeReference<?> bodyElementType;
private HttpRequestValues(HttpMethod httpMethod, @Nullable URI uri,
@Nullable String uriTemplate, @Nullable Map<String, String> uriVariables,
@Nullable HttpHeaders headers, @Nullable MultiValueMap<String, String> cookies,
@Nullable Object bodyValue,
@Nullable Publisher<?> body,
@Nullable ParameterizedTypeReference<?> bodyElementType) {
Assert.isTrue(uri == null || uriTemplate == null, "Expected either URI or URI template, not both");
this.httpMethod = httpMethod;
this.uri = uri;
this.uriTemplate = (uri != null || uriTemplate != null ? uriTemplate : "");
this.uriVariables = (uriVariables != null ? uriVariables : Collections.emptyMap());
this.headers = (headers != null ? headers : HttpHeaders.EMPTY);
this.cookies = (cookies != null ? cookies : EMPTY_COOKIES_MAP);
this.bodyValue = bodyValue;
this.body = body;
this.bodyElementType = bodyElementType;
}
/**
* Return the HTTP method to use for the request.
*/
public HttpMethod getHttpMethod() {
return this.httpMethod;
}
/**
* Return the full URL to use, if set.
* <p>This is mutually exclusive with {@link #getUriTemplate() uriTemplate}.
* One of the two has a value but not both.
*/
@Nullable
public URI getUri() {
return this.uri;
}
/**
* Return the URL template for the request, if set.
* <p>This is mutually exclusive with a {@link #getUri() full URL}.
* One of the two has a value but not both.
*/
@Nullable
public String getUriTemplate() {
return this.uriTemplate;
}
/**
* Return the URL template variables, or an empty map.
*/
public Map<String, String> getUriVariables() {
return this.uriVariables;
}
/**
* Return the headers for the request, if any.
*/
public HttpHeaders getHeaders() {
return this.headers;
}
/**
* Return the cookies for the request, if any.
*/
public MultiValueMap<String, String> getCookies() {
return this.cookies;
}
/**
* Return the request body as a value to be serialized, if set.
* <p>This is mutually exclusive with {@link #getBody()}.
* Only one of the two or neither is set.
*/
@Nullable
public Object getBodyValue() {
return this.bodyValue;
}
/**
* Return the request body as a Publisher.
* <p>This is mutually exclusive with {@link #getBodyValue()}.
* Only one of the two or neither is set.
*/
@Nullable
public Publisher<?> getBody() {
return this.body;
}
/**
* Return the element type for a {@link #getBody() Publisher body}.
*/
@Nullable
public ParameterizedTypeReference<?> getBodyElementType() {
return this.bodyElementType;
}
public static Builder builder(HttpMethod httpMethod) {
return new Builder(httpMethod);
}
/**
* Builder for {@link HttpRequestValues}.
*/
public final static class Builder {
private HttpMethod httpMethod;
@Nullable
private URI uri;
@Nullable
private String uriTemplate;
@Nullable
private Map<String, String> uriVariables;
@Nullable
private HttpHeaders headers;
@Nullable
private MultiValueMap<String, String> cookies;
@Nullable
private Object bodyValue;
@Nullable
private Publisher<?> body;
@Nullable
private ParameterizedTypeReference<?> bodyElementType;
private Builder(HttpMethod httpMethod) {
Assert.notNull(httpMethod, "HttpMethod is required");
this.httpMethod = httpMethod;
}
/**
* Set the HTTP method for the request.
*/
public Builder setHttpMethod(HttpMethod httpMethod) {
Assert.notNull(httpMethod, "HttpMethod is required");
this.httpMethod = httpMethod;
return this;
}
/**
* Set the request URL as a full URL.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setUriTemplate(String)}.
*/
public Builder setUri(URI uri) {
this.uri = uri;
this.uriTemplate = null;
return this;
}
/**
* Set the request URL as a String template.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setUri(URI) full URI}.
*/
public Builder setUriTemplate(String uriTemplate) {
this.uriTemplate = uriTemplate;
this.uri = null;
return this;
}
/**
* Add a URI variable name-value pair.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setUri(URI) full URI}.
*/
public Builder setUriVariable(String name, String value) {
this.uriVariables = (this.uriVariables != null ? this.uriVariables : new LinkedHashMap<>());
this.uriVariables.put(name, value);
this.uri = null;
return this;
}
/**
* Set the media types for the request {@code Accept} header.
*/
public Builder setAccept(List<MediaType> acceptableMediaTypes) {
initHeaders().setAccept(acceptableMediaTypes);
return this;
}
/**
* Set the media type for the request {@code Content-Type} header.
*/
public Builder setContentType(MediaType contentType) {
initHeaders().setContentType(contentType);
return this;
}
/**
* Add the given header name and values.
*/
public Builder addHeader(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
initHeaders().add(headerName, headerValue);
}
return this;
}
private HttpHeaders initHeaders() {
this.headers = (this.headers != null ? this.headers : new HttpHeaders());
return this.headers;
}
/**
* Add the given cookie name and values.
*/
public Builder addCookie(String name, String... values) {
this.cookies = (this.cookies != null ? this.cookies : new LinkedMultiValueMap<>());
for (String value : values) {
this.cookies.add(name, value);
}
return this;
}
/**
* Set the request body as a concrete value to be serialized.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setBody(Publisher, ParameterizedTypeReference) body Publisher}.
*/
public void setBodyValue(Object bodyValue) {
this.bodyValue = bodyValue;
this.body = null;
this.bodyElementType = null;
}
/**
* Set the request body as a concrete value to be serialized.
* <p>This is mutually exclusive with, and resets any previously set
* {@link #setBodyValue(Object) body value}.
*/
public <T, P extends Publisher<T>> void setBody(Publisher<P> body, ParameterizedTypeReference<?> elementTye) {
this.body = body;
this.bodyElementType = elementTye;
this.bodyValue = null;
}
/**
* Builder the {@link HttpRequestValues} instance.
*/
public HttpRequestValues build() {
return new HttpRequestValues(
this.httpMethod, this.uri, this.uriTemplate, this.uriVariables,
this.headers, this.cookies,
this.bodyValue, this.body, this.bodyElementType);
}
}
}

View File

@ -34,8 +34,8 @@ public interface HttpServiceArgumentResolver {
* Resolve the argument value. * Resolve the argument value.
* @param argument the argument value * @param argument the argument value
* @param parameter the method parameter for the argument * @param parameter the method parameter for the argument
* @param requestSpec container to add HTTP request values to * @param requestValues builder to add HTTP request values to
*/ */
void resolve(@Nullable Object argument, MethodParameter parameter, HttpRequestSpec requestSpec); void resolve(@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues);
} }

View File

@ -62,7 +62,7 @@ final class HttpServiceMethod {
private final List<HttpServiceArgumentResolver> argumentResolvers; private final List<HttpServiceArgumentResolver> argumentResolvers;
private final HttpRequestSpecFactory requestSpecFactory; private final HttpRequestValuesInitializer requestValuesInitializer;
private final ResponseFunction responseFunction; private final ResponseFunction responseFunction;
@ -75,7 +75,7 @@ final class HttpServiceMethod {
this.method = method; this.method = method;
this.parameters = initMethodParameters(method); this.parameters = initMethodParameters(method);
this.argumentResolvers = argumentResolvers; this.argumentResolvers = argumentResolvers;
this.requestSpecFactory = HttpRequestSpecFactory.create(method, containingClass); this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass);
this.responseFunction = ResponseFunction.create(client, method, reactiveRegistry, blockTimeout); this.responseFunction = ResponseFunction.create(client, method, reactiveRegistry, blockTimeout);
} }
@ -96,35 +96,34 @@ final class HttpServiceMethod {
@Nullable @Nullable
public Object invoke(Object[] arguments) { public Object invoke(Object[] arguments) {
HttpRequestSpec requestSpec = this.requestSpecFactory.initializeRequestSpec(); HttpRequestValues.Builder requestValues = this.requestValuesInitializer.initializeRequestValuesBuilder();
applyArguments(requestSpec, arguments); applyArguments(requestValues, arguments);
requestSpec.setComplete(); return this.responseFunction.execute(requestValues.build());
return this.responseFunction.execute(requestSpec);
} }
private void applyArguments(HttpRequestSpec requestSpec, Object[] arguments) { private void applyArguments(HttpRequestValues.Builder requestValues, Object[] arguments) {
Assert.isTrue(arguments.length == this.parameters.length, "Method argument mismatch"); Assert.isTrue(arguments.length == this.parameters.length, "Method argument mismatch");
for (int i = 0; i < this.parameters.length; i++) { for (int i = 0; i < this.parameters.length; i++) {
Object argumentValue = arguments[i]; Object argumentValue = arguments[i];
ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
this.parameters[i].initParameterNameDiscovery(nameDiscoverer); this.parameters[i].initParameterNameDiscovery(nameDiscoverer);
for (HttpServiceArgumentResolver resolver : this.argumentResolvers) { for (HttpServiceArgumentResolver resolver : this.argumentResolvers) {
resolver.resolve(argumentValue, this.parameters[i], requestSpec); resolver.resolve(argumentValue, this.parameters[i], requestValues);
} }
} }
} }
/** /**
* Factory for an {@link HttpRequestSpec} with values extracted from * Factory for an {@link HttpRequestValues} with values extracted from
* the type and method-level {@link HttpExchange @HttpRequest} annotations. * the type and method-level {@link HttpExchange @HttpRequest} annotations.
*/ */
private record HttpRequestSpecFactory( private record HttpRequestValuesInitializer(
@Nullable HttpMethod httpMethod, @Nullable String url, HttpMethod httpMethod, @Nullable String url,
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) { @Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
private HttpRequestSpecFactory( private HttpRequestValuesInitializer(
@Nullable HttpMethod httpMethod, @Nullable String url, HttpMethod httpMethod, @Nullable String url,
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) { @Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
this.url = url; this.url = url;
@ -133,28 +132,25 @@ final class HttpServiceMethod {
this.acceptMediaTypes = acceptMediaTypes; this.acceptMediaTypes = acceptMediaTypes;
} }
public HttpRequestSpec initializeRequestSpec() { public HttpRequestValues.Builder initializeRequestValuesBuilder() {
HttpRequestSpec requestSpec = new HttpRequestSpec(); HttpRequestValues.Builder requestValues = HttpRequestValues.builder(this.httpMethod);
if (this.httpMethod != null) {
requestSpec.setHttpMethod(this.httpMethod);
}
if (this.url != null) { if (this.url != null) {
requestSpec.setUriTemplate(this.url); requestValues.setUriTemplate(this.url);
} }
if (this.contentType != null) { if (this.contentType != null) {
requestSpec.getHeaders().setContentType(this.contentType); requestValues.setContentType(this.contentType);
} }
if (this.acceptMediaTypes != null) { if (this.acceptMediaTypes != null) {
requestSpec.getHeaders().setAccept(this.acceptMediaTypes); requestValues.setAccept(this.acceptMediaTypes);
} }
return requestSpec; return requestValues;
} }
/** /**
* Introspect the method and create the request factory for it. * Introspect the method and create the request factory for it.
*/ */
public static HttpRequestSpecFactory create(Method method, Class<?> containingClass) { public static HttpRequestValuesInitializer create(Method method, Class<?> containingClass) {
HttpExchange annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class); HttpExchange annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class);
HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class); HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class);
@ -166,11 +162,10 @@ final class HttpServiceMethod {
MediaType contentType = initContentType(annot1, annot2); MediaType contentType = initContentType(annot1, annot2);
List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2); List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2);
return new HttpRequestSpecFactory(httpMethod, url, contentType, acceptableMediaTypes); return new HttpRequestValuesInitializer(httpMethod, url, contentType, acceptableMediaTypes);
} }
@Nullable
private static HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnot, HttpExchange annot) { private static HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnot, HttpExchange annot) {
String value1 = (typeAnnot != null ? typeAnnot.method() : null); String value1 = (typeAnnot != null ? typeAnnot.method() : null);
@ -184,7 +179,7 @@ final class HttpServiceMethod {
return HttpMethod.valueOf(value1); return HttpMethod.valueOf(value1);
} }
return null; throw new IllegalStateException("HttpMethod is required");
} }
@Nullable @Nullable
@ -249,12 +244,12 @@ final class HttpServiceMethod {
* return type blocking if necessary. * return type blocking if necessary.
*/ */
private record ResponseFunction( private record ResponseFunction(
Function<HttpRequestSpec, Publisher<?>> responseFunction, Function<HttpRequestValues, Publisher<?>> responseFunction,
@Nullable ReactiveAdapter returnTypeAdapter, @Nullable ReactiveAdapter returnTypeAdapter,
boolean blockForOptional, Duration blockTimeout) { boolean blockForOptional, Duration blockTimeout) {
private ResponseFunction( private ResponseFunction(
Function<HttpRequestSpec, Publisher<?>> responseFunction, Function<HttpRequestValues, Publisher<?>> responseFunction,
@Nullable ReactiveAdapter returnTypeAdapter, @Nullable ReactiveAdapter returnTypeAdapter,
boolean blockForOptional, Duration blockTimeout) { boolean blockForOptional, Duration blockTimeout) {
@ -265,9 +260,9 @@ final class HttpServiceMethod {
} }
@Nullable @Nullable
public Object execute(HttpRequestSpec requestSpec) { public Object execute(HttpRequestValues requestValues) {
Publisher<?> responsePublisher = this.responseFunction.apply(requestSpec); Publisher<?> responsePublisher = this.responseFunction.apply(requestValues);
if (this.returnTypeAdapter != null) { if (this.returnTypeAdapter != null) {
return this.returnTypeAdapter.fromPublisher(responsePublisher); return this.returnTypeAdapter.fromPublisher(responsePublisher);
@ -293,7 +288,7 @@ final class HttpServiceMethod {
MethodParameter actualParam = (reactiveAdapter != null ? returnParam.nested() : returnParam.nestedIfOptional()); MethodParameter actualParam = (reactiveAdapter != null ? returnParam.nested() : returnParam.nestedIfOptional());
Class<?> actualType = actualParam.getNestedParameterType(); Class<?> actualType = actualParam.getNestedParameterType();
Function<HttpRequestSpec, Publisher<?>> responseFunction; Function<HttpRequestValues, Publisher<?>> responseFunction;
if (actualType.equals(void.class) || actualType.equals(Void.class)) { if (actualType.equals(void.class) || actualType.equals(Void.class)) {
responseFunction = client::requestToVoid; responseFunction = client::requestToVoid;
} }
@ -323,7 +318,7 @@ final class HttpServiceMethod {
} }
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
private static Function<HttpRequestSpec, Publisher<?>> initResponseEntityFunction( private static Function<HttpRequestValues, Publisher<?>> initResponseEntityFunction(
HttpClientAdapter client, MethodParameter methodParam, @Nullable ReactiveAdapter reactiveAdapter) { HttpClientAdapter client, MethodParameter methodParam, @Nullable ReactiveAdapter reactiveAdapter) {
if (reactiveAdapter == null) { if (reactiveAdapter == null) {
@ -349,7 +344,7 @@ final class HttpServiceMethod {
}); });
} }
private static Function<HttpRequestSpec, Publisher<?>> initBodyFunction( private static Function<HttpRequestValues, Publisher<?>> initBodyFunction(
HttpClientAdapter client, MethodParameter methodParam, @Nullable ReactiveAdapter reactiveAdapter) { HttpClientAdapter client, MethodParameter methodParam, @Nullable ReactiveAdapter reactiveAdapter) {
ParameterizedTypeReference<?> bodyType = ParameterizedTypeReference<?> bodyType =

View File

@ -56,7 +56,7 @@ public class PathVariableArgumentResolver implements HttpServiceArgumentResolver
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public void resolve( public void resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestSpec requestSpec) { @Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class); PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
if (annotation == null) { if (annotation == null) {
@ -67,38 +67,38 @@ public class PathVariableArgumentResolver implements HttpServiceArgumentResolver
if (argument != null) { if (argument != null) {
Assert.isInstanceOf(Map.class, argument); Assert.isInstanceOf(Map.class, argument);
((Map<String, ?>) argument).forEach((key, value) -> ((Map<String, ?>) argument).forEach((key, value) ->
addUriParameter(key, value, annotation.required(), requestSpec)); addUriParameter(key, value, annotation.required(), requestValues));
} }
} }
else { else {
String name = StringUtils.hasText(annotation.value()) ? annotation.value() : annotation.name(); String name = StringUtils.hasText(annotation.value()) ? annotation.value() : annotation.name();
name = StringUtils.hasText(name) ? name : parameter.getParameterName(); name = StringUtils.hasText(name) ? name : parameter.getParameterName();
Assert.notNull(name, "Failed to determine path variable name for parameter: " + parameter); Assert.notNull(name, "Failed to determine path variable name for parameter: " + parameter);
addUriParameter(name, argument, annotation.required(), requestSpec); addUriParameter(name, argument, annotation.required(), requestValues);
} }
} }
private void addUriParameter( private void addUriParameter(
String name, @Nullable Object value, boolean required, HttpRequestSpec requestSpec) { String name, @Nullable Object value, boolean required, HttpRequestValues.Builder requestValues) {
if (value instanceof Optional) { if (value instanceof Optional) {
value = ((Optional<?>) value).orElse(null); value = ((Optional<?>) value).orElse(null);
} }
if (!(value instanceof String)) {
value = this.conversionService.convert(value, String.class);
}
if (value == null) { if (value == null) {
Assert.isTrue(!required, "Missing required path variable '" + name + "'"); Assert.isTrue(!required, "Missing required path variable '" + name + "'");
return; return;
} }
if (!(value instanceof String)) {
value = this.conversionService.convert(value, String.class);
}
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Resolved path variable '" + name + "' to " + value); logger.trace("Resolved path variable '" + name + "' to " + value);
} }
requestSpec.getUriVariables().put(name, (String) value); requestValues.setUriVariable(name, (String) value);
} }
} }

View File

@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -47,7 +46,7 @@ public class HttpMethodArgumentResolverTests {
@Test @Test
void shouldIgnoreArgumentsNotMatchingType() { void shouldIgnoreArgumentsNotMatchingType() {
this.service.execute("test"); this.service.execute("test");
assertThat(getActualMethod()).isNull(); assertThat(getActualMethod()).isEqualTo(HttpMethod.GET);
} }
@Test @Test
@ -59,30 +58,29 @@ public class HttpMethodArgumentResolverTests {
@Test @Test
void shouldIgnoreNullValue() { void shouldIgnoreNullValue() {
this.service.executeForNull(null); this.service.executeForNull(null);
assertThat(getActualMethod()).isNull(); assertThat(getActualMethod()).isEqualTo(HttpMethod.GET);
} }
@Nullable
private HttpMethod getActualMethod() { private HttpMethod getActualMethod() {
return this.clientAdapter.getRequestSpec().getHttpMethod(); return this.clientAdapter.getRequestValues().getHttpMethod();
} }
private interface Service { private interface Service {
@HttpExchange @GetExchange
void execute(HttpMethod method); void execute(HttpMethod method);
@GetExchange @GetExchange
void executeGet(HttpMethod method); void executeGet(HttpMethod method);
@HttpExchange @GetExchange
void execute(String test); void execute(String test);
@HttpExchange @GetExchange
void execute(HttpMethod firstMethod, HttpMethod secondMethod); void execute(HttpMethod firstMethod, HttpMethod secondMethod);
@HttpExchange @GetExchange
void executeForNull(@Nullable HttpMethod method); void executeForNull(@Nullable HttpMethod method);
} }

View File

@ -141,45 +141,45 @@ public class HttpServiceMethodTests {
@Test @Test
void methodAnnotatedService() { void methodAnnotatedService() {
MethodAnnotatedService service = this.clientAdapter.createService(MethodAnnotatedService.class); MethodLevelAnnotatedService service = this.clientAdapter.createService(MethodLevelAnnotatedService.class);
service.performGet(); service.performGet();
HttpRequestSpec request = this.clientAdapter.getRequestSpec(); HttpRequestValues requestValues = this.clientAdapter.getRequestValues();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.GET); assertThat(requestValues.getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(request.getUriTemplate()).isNull(); assertThat(requestValues.getUriTemplate()).isEqualTo("");
assertThat(request.getHeaders().getContentType()).isNull(); assertThat(requestValues.getHeaders().getContentType()).isNull();
assertThat(request.getHeaders().getAccept()).isEmpty(); assertThat(requestValues.getHeaders().getAccept()).isEmpty();
service.performPost(); service.performPost();
request = this.clientAdapter.getRequestSpec(); requestValues = this.clientAdapter.getRequestValues();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST); assertThat(requestValues.getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(request.getUriTemplate()).isEqualTo("/url"); assertThat(requestValues.getUriTemplate()).isEqualTo("/url");
assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON); assertThat(requestValues.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
assertThat(request.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_JSON); assertThat(requestValues.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_JSON);
} }
@Test @Test
void typeAndMethodAnnotatedService() { void typeAndMethodAnnotatedService() {
MethodAnnotatedService service = this.clientAdapter.createService(TypeAndMethodAnnotatedService.class); MethodLevelAnnotatedService service = this.clientAdapter.createService(TypeAndMethodLevelAnnotatedService.class);
service.performGet(); service.performGet();
HttpRequestSpec request = this.clientAdapter.getRequestSpec(); HttpRequestValues requestValues = this.clientAdapter.getRequestValues();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.GET); assertThat(requestValues.getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(request.getUriTemplate()).isEqualTo("/base"); assertThat(requestValues.getUriTemplate()).isEqualTo("/base");
assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_CBOR); assertThat(requestValues.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_CBOR);
assertThat(request.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_CBOR); assertThat(requestValues.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_CBOR);
service.performPost(); service.performPost();
request = this.clientAdapter.getRequestSpec(); requestValues = this.clientAdapter.getRequestValues();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST); assertThat(requestValues.getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(request.getUriTemplate()).isEqualTo("/base/url"); assertThat(requestValues.getUriTemplate()).isEqualTo("/base/url");
assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON); assertThat(requestValues.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
assertThat(request.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_JSON); assertThat(requestValues.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_JSON);
} }
private void verifyClientInvocation(String methodName, @Nullable ParameterizedTypeReference<?> expectedBodyType) { private void verifyClientInvocation(String methodName, @Nullable ParameterizedTypeReference<?> expectedBodyType) {
@ -191,7 +191,7 @@ public class HttpServiceMethodTests {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private interface ReactorService { private interface ReactorService {
@HttpExchange @GetExchange
Mono<Void> execute(); Mono<Void> execute();
@GetExchange @GetExchange
@ -217,7 +217,7 @@ public class HttpServiceMethodTests {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private interface RxJavaService { private interface RxJavaService {
@HttpExchange @GetExchange
Completable execute(); Completable execute();
@GetExchange @GetExchange
@ -243,7 +243,7 @@ public class HttpServiceMethodTests {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private interface BlockingService { private interface BlockingService {
@HttpExchange @GetExchange
void execute(); void execute();
@GetExchange @GetExchange
@ -261,7 +261,7 @@ public class HttpServiceMethodTests {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private interface MethodAnnotatedService { private interface MethodLevelAnnotatedService {
@GetExchange @GetExchange
void performGet(); void performGet();
@ -274,7 +274,7 @@ public class HttpServiceMethodTests {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@HttpExchange(url = "/base", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE) @HttpExchange(url = "/base", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE)
private interface TypeAndMethodAnnotatedService extends MethodAnnotatedService { private interface TypeAndMethodLevelAnnotatedService extends MethodLevelAnnotatedService {
} }
} }

View File

@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.HttpExchange; import org.springframework.web.service.annotation.GetExchange;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@ -143,44 +143,44 @@ class PathVariableArgumentResolverTests {
} }
private Map<String, String> getActualUriVariables() { private Map<String, String> getActualUriVariables() {
return this.clientAdapter.getRequestSpec().getUriVariables(); return this.clientAdapter.getRequestValues().getUriVariables();
} }
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private interface Service { private interface Service {
@HttpExchange @GetExchange
void execute(@PathVariable String id); void execute(@PathVariable String id);
@HttpExchange @GetExchange
void executeNotRequired(@Nullable @PathVariable(required = false) String id); void executeNotRequired(@Nullable @PathVariable(required = false) String id);
@HttpExchange @GetExchange
void executeOptional(@PathVariable Optional<Boolean> id); void executeOptional(@PathVariable Optional<Boolean> id);
@HttpExchange @GetExchange
void executeOptionalNotRequired(@PathVariable(required = false) Optional<String> id); void executeOptionalNotRequired(@PathVariable(required = false) Optional<String> id);
@HttpExchange @GetExchange
void executeNamedWithValue(@Nullable @PathVariable(name = "test", value = "id") String employeeId); void executeNamedWithValue(@Nullable @PathVariable(name = "test", value = "id") String employeeId);
@HttpExchange @GetExchange
void executeNamed(@PathVariable(name = "id") String employeeId); void executeNamed(@PathVariable(name = "id") String employeeId);
@HttpExchange @GetExchange
void executeValueNamed(@PathVariable("id") String employeeId); void executeValueNamed(@PathVariable("id") String employeeId);
@HttpExchange @GetExchange
void execute(@PathVariable Object id); void execute(@PathVariable Object id);
@HttpExchange @GetExchange
void execute(@PathVariable Boolean id); void execute(@PathVariable Boolean id);
@HttpExchange @GetExchange
void executeValueMap(@Nullable @PathVariable Map<String, String> map); void executeValueMap(@Nullable @PathVariable Map<String, String> map);
@HttpExchange @GetExchange
void executeOptionalValueMap(@PathVariable Map<String, Optional<String>> map); void executeOptionalValueMap(@PathVariable Map<String, Optional<String>> map);
} }

View File

@ -44,7 +44,7 @@ class TestHttpClientAdapter implements HttpClientAdapter {
private String invokedMethodName; private String invokedMethodName;
@Nullable @Nullable
private HttpRequestSpec requestSpec; private HttpRequestValues requestValues;
@Nullable @Nullable
private ParameterizedTypeReference<?> bodyType; private ParameterizedTypeReference<?> bodyType;
@ -67,9 +67,9 @@ class TestHttpClientAdapter implements HttpClientAdapter {
return this.invokedMethodName; return this.invokedMethodName;
} }
public HttpRequestSpec getRequestSpec() { public HttpRequestValues getRequestValues() {
assertThat(this.requestSpec).isNotNull(); assertThat(this.requestValues).isNotNull();
return this.requestSpec; return this.requestValues;
} }
@Nullable @Nullable
@ -81,56 +81,56 @@ class TestHttpClientAdapter implements HttpClientAdapter {
// HttpClientAdapter implementation // HttpClientAdapter implementation
@Override @Override
public Mono<Void> requestToVoid(HttpRequestSpec requestSpec) { public Mono<Void> requestToVoid(HttpRequestValues requestValues) {
saveInput("requestToVoid", requestSpec, null); saveInput("requestToVoid", requestValues, null);
return Mono.empty(); return Mono.empty();
} }
@Override @Override
public Mono<HttpHeaders> requestToHeaders(HttpRequestSpec requestSpec) { public Mono<HttpHeaders> requestToHeaders(HttpRequestValues requestValues) {
saveInput("requestToHeaders", requestSpec, null); saveInput("requestToHeaders", requestValues, null);
return Mono.just(new HttpHeaders()); return Mono.just(new HttpHeaders());
} }
@Override @Override
public <T> Mono<T> requestToBody(HttpRequestSpec requestSpec, ParameterizedTypeReference<T> bodyType) { public <T> Mono<T> requestToBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
saveInput("requestToBody", requestSpec, bodyType); saveInput("requestToBody", requestValues, bodyType);
return (Mono<T>) Mono.just(getInvokedMethodName()); return (Mono<T>) Mono.just(getInvokedMethodName());
} }
@Override @Override
public <T> Flux<T> requestToBodyFlux(HttpRequestSpec requestSpec, ParameterizedTypeReference<T> bodyType) { public <T> Flux<T> requestToBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
saveInput("requestToBodyFlux", requestSpec, bodyType); saveInput("requestToBodyFlux", requestValues, bodyType);
return (Flux<T>) Flux.just("request", "To", "Body", "Flux"); return (Flux<T>) Flux.just("request", "To", "Body", "Flux");
} }
@Override @Override
public Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestSpec requestSpec) { public Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestValues requestValues) {
saveInput("requestToBodilessEntity", requestSpec, null); saveInput("requestToBodilessEntity", requestValues, null);
return Mono.just(ResponseEntity.ok().build()); return Mono.just(ResponseEntity.ok().build());
} }
@Override @Override
public <T> Mono<ResponseEntity<T>> requestToEntity( public <T> Mono<ResponseEntity<T>> requestToEntity(
HttpRequestSpec requestSpec, ParameterizedTypeReference<T> type) { HttpRequestValues requestValues, ParameterizedTypeReference<T> type) {
saveInput("requestToEntity", requestSpec, type); saveInput("requestToEntity", requestValues, type);
return Mono.just((ResponseEntity<T>) ResponseEntity.ok("requestToEntity")); return Mono.just((ResponseEntity<T>) ResponseEntity.ok("requestToEntity"));
} }
@Override @Override
public <T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux( public <T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(
HttpRequestSpec requestSpec, ParameterizedTypeReference<T> bodyType) { HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
saveInput("requestToEntityFlux", requestSpec, bodyType); saveInput("requestToEntityFlux", requestValues, bodyType);
return Mono.just(ResponseEntity.ok((Flux<T>) Flux.just("request", "To", "Entity", "Flux"))); return Mono.just(ResponseEntity.ok((Flux<T>) Flux.just("request", "To", "Entity", "Flux")));
} }
private <T> void saveInput( private <T> void saveInput(
String methodName, HttpRequestSpec requestSpec, @Nullable ParameterizedTypeReference<T> bodyType) { String methodName, HttpRequestValues requestValues, @Nullable ParameterizedTypeReference<T> bodyType) {
this.invokedMethodName = methodName; this.invokedMethodName = methodName;
this.requestSpec = requestSpec; this.requestValues = requestValues;
this.bodyType = bodyType; this.bodyType = bodyType;
} }

View File

@ -24,10 +24,11 @@ import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.service.invoker.HttpClientAdapter; import org.springframework.web.service.invoker.HttpClientAdapter;
import org.springframework.web.service.invoker.HttpRequestSpec; import org.springframework.web.service.invoker.HttpRequestValues;
/** /**
@ -47,67 +48,68 @@ public class WebClientAdapter implements HttpClientAdapter {
@Override @Override
public Mono<Void> requestToVoid(HttpRequestSpec requestSpec) { public Mono<Void> requestToVoid(HttpRequestValues requestValues) {
return toBodySpec(requestSpec).exchangeToMono(ClientResponse::releaseBody); return toBodySpec(requestValues).exchangeToMono(ClientResponse::releaseBody);
} }
@Override @Override
public Mono<HttpHeaders> requestToHeaders(HttpRequestSpec requestSpec) { public Mono<HttpHeaders> requestToHeaders(HttpRequestValues requestValues) {
return toBodySpec(requestSpec).retrieve().toBodilessEntity().map(ResponseEntity::getHeaders); return toBodySpec(requestValues).retrieve().toBodilessEntity().map(ResponseEntity::getHeaders);
} }
@Override @Override
public <T> Mono<T> requestToBody(HttpRequestSpec reqrequestSpecest, ParameterizedTypeReference<T> bodyType) { public <T> Mono<T> requestToBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return toBodySpec(reqrequestSpecest).retrieve().bodyToMono(bodyType); return toBodySpec(requestValues).retrieve().bodyToMono(bodyType);
} }
@Override @Override
public <T> Flux<T> requestToBodyFlux(HttpRequestSpec requestSpec, ParameterizedTypeReference<T> bodyType) { public <T> Flux<T> requestToBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return toBodySpec(requestSpec).retrieve().bodyToFlux(bodyType); return toBodySpec(requestValues).retrieve().bodyToFlux(bodyType);
} }
@Override @Override
public Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestSpec requestSpec) { public Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestValues requestValues) {
return toBodySpec(requestSpec).retrieve().toBodilessEntity(); return toBodySpec(requestValues).retrieve().toBodilessEntity();
} }
@Override @Override
public <T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType) { public <T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return toBodySpec(spec).retrieve().toEntity(bodyType); return toBodySpec(requestValues).retrieve().toEntity(bodyType);
} }
@Override @Override
public <T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestSpec spec, ParameterizedTypeReference<T> bodyType) { public <T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return toBodySpec(spec).retrieve().toEntityFlux(bodyType); return toBodySpec(requestValues).retrieve().toEntityFlux(bodyType);
} }
@SuppressWarnings("ReactiveStreamsUnusedPublisher") @SuppressWarnings("ReactiveStreamsUnusedPublisher")
private WebClient.RequestBodySpec toBodySpec(HttpRequestSpec requestSpec) { private WebClient.RequestBodySpec toBodySpec(HttpRequestValues requestValues) {
HttpMethod httpMethod = requestValues.getHttpMethod();
Assert.notNull(httpMethod, "No HttpMethod");
HttpMethod httpMethod = requestSpec.getHttpMethodRequired();
WebClient.RequestBodyUriSpec uriSpec = this.webClient.method(httpMethod); WebClient.RequestBodyUriSpec uriSpec = this.webClient.method(httpMethod);
WebClient.RequestBodySpec bodySpec; WebClient.RequestBodySpec bodySpec;
if (requestSpec.getUri() != null) { if (requestValues.getUri() != null) {
bodySpec = uriSpec.uri(requestSpec.getUri()); bodySpec = uriSpec.uri(requestValues.getUri());
} }
else if (requestSpec.getUriTemplate() != null) { else if (requestValues.getUriTemplate() != null) {
bodySpec = (!requestSpec.getUriVariables().isEmpty() ? bodySpec = uriSpec.uri(requestValues.getUriTemplate(), requestValues.getUriVariables());
uriSpec.uri(requestSpec.getUriTemplate(), requestSpec.getUriVariables()) :
uriSpec.uri(requestSpec.getUriTemplate(), requestSpec.getUriVariableValues()));
} }
else { else {
bodySpec = uriSpec.uri(""); throw new IllegalStateException("Neither full URL nor URI template");
} }
bodySpec.headers(headers -> headers.putAll(requestSpec.getHeaders())); bodySpec.headers(headers -> headers.putAll(requestValues.getHeaders()));
bodySpec.cookies(cookies -> cookies.putAll(requestSpec.getCookies())); bodySpec.cookies(cookies -> cookies.putAll(requestValues.getCookies()));
if (requestSpec.getBodyValue() != null) { if (requestValues.getBodyValue() != null) {
bodySpec.bodyValue(requestSpec.getBodyValue()); bodySpec.bodyValue(requestValues.getBodyValue());
} }
else if (requestSpec.getBodyPublisher() != null) { else if (requestValues.getBody() != null) {
bodySpec.body(requestSpec.getBodyPublisher(), requestSpec.getBodyPublisherElementType()); Assert.notNull(requestValues.getBodyElementType(), "Publisher body element type is required");
bodySpec.body(requestValues.getBody(), requestValues.getBodyElementType());
} }
return bodySpec; return bodySpec;