Add ReactiveHttpRequestValues
Separate collection and handling of reactive request values into a subclass of HttpRequestValues. Closes gh-30117
This commit is contained in:
parent
3209cf5c7a
commit
a3e37597aa
|
|
@ -27,10 +27,10 @@ import org.reactivestreams.Publisher;
|
|||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.MultipartBodyBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
|
@ -48,7 +48,7 @@ import org.springframework.web.util.UriUtils;
|
|||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class HttpRequestValues {
|
||||
public class HttpRequestValues {
|
||||
|
||||
private static final MultiValueMap<String, String> EMPTY_COOKIES_MAP =
|
||||
CollectionUtils.toMultiValueMap(Collections.emptyMap());
|
||||
|
|
@ -74,18 +74,11 @@ public final class HttpRequestValues {
|
|||
@Nullable
|
||||
private final Object bodyValue;
|
||||
|
||||
@Nullable
|
||||
private final Publisher<?> body;
|
||||
|
||||
@Nullable
|
||||
private final ParameterizedTypeReference<?> bodyElementType;
|
||||
|
||||
|
||||
private HttpRequestValues(@Nullable HttpMethod httpMethod,
|
||||
protected HttpRequestValues(@Nullable HttpMethod httpMethod,
|
||||
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
|
||||
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
|
||||
@Nullable Object bodyValue,
|
||||
@Nullable Publisher<?> body, @Nullable ParameterizedTypeReference<?> bodyElementType) {
|
||||
@Nullable Object bodyValue) {
|
||||
|
||||
Assert.isTrue(uri != null || uriTemplate != null, "Neither URI nor URI template");
|
||||
|
||||
|
|
@ -97,8 +90,6 @@ public final class HttpRequestValues {
|
|||
this.cookies = cookies;
|
||||
this.attributes = attributes;
|
||||
this.bodyValue = bodyValue;
|
||||
this.body = body;
|
||||
this.bodyElementType = bodyElementType;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -161,8 +152,6 @@ public final class HttpRequestValues {
|
|||
|
||||
/**
|
||||
* 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() {
|
||||
|
|
@ -173,18 +162,24 @@ public final class HttpRequestValues {
|
|||
* Return the request body as a Publisher.
|
||||
* <p>This is mutually exclusive with {@link #getBodyValue()}.
|
||||
* Only one of the two or neither is set.
|
||||
* @deprecated in favor of {@link ReactiveHttpRequestValues#getBodyPublisher()};
|
||||
* to be removed in 6.2
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
@Nullable
|
||||
public Publisher<?> getBody() {
|
||||
return this.body;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the element type for a {@linkplain #getBody() Publisher body}.
|
||||
* Return the element type for a Publisher body.
|
||||
* @deprecated in favor of {@link ReactiveHttpRequestValues#getBodyPublisherElementType()};
|
||||
* to be removed in 6.2
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
@Nullable
|
||||
public ParameterizedTypeReference<?> getBodyElementType() {
|
||||
return this.bodyElementType;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -196,7 +191,7 @@ public final class HttpRequestValues {
|
|||
/**
|
||||
* Builder for {@link HttpRequestValues}.
|
||||
*/
|
||||
public final static class Builder {
|
||||
public static class Builder {
|
||||
|
||||
@Nullable
|
||||
private HttpMethod httpMethod;
|
||||
|
|
@ -220,7 +215,7 @@ public final class HttpRequestValues {
|
|||
private MultiValueMap<String, String> requestParams;
|
||||
|
||||
@Nullable
|
||||
private MultipartBodyBuilder multipartBuilder;
|
||||
private MultiValueMap<String, Object> parts;
|
||||
|
||||
@Nullable
|
||||
private Map<String, Object> attributes;
|
||||
|
|
@ -228,12 +223,6 @@ public final class HttpRequestValues {
|
|||
@Nullable
|
||||
private Object bodyValue;
|
||||
|
||||
@Nullable
|
||||
private Publisher<?> body;
|
||||
|
||||
@Nullable
|
||||
private ParameterizedTypeReference<?> bodyElementType;
|
||||
|
||||
/**
|
||||
* Set the HTTP method for the request.
|
||||
*/
|
||||
|
|
@ -327,23 +316,30 @@ public final class HttpRequestValues {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add a part to a multipart request. The part value may be as described
|
||||
* in {@link MultipartBodyBuilder#part(String, Object)}.
|
||||
* Add a part for a multipart request. The part may be:
|
||||
* <ul>
|
||||
* <li>String -- form field
|
||||
* <li>{@link org.springframework.core.io.Resource Resource} -- file part
|
||||
* <li>Object -- content to be encoded (e.g. to JSON)
|
||||
* <li>{@link HttpEntity} -- part content and headers although generally it's
|
||||
* easier to add headers through the returned builder
|
||||
* </ul>
|
||||
*/
|
||||
public Builder addRequestPart(String name, Object part) {
|
||||
this.multipartBuilder = (this.multipartBuilder != null ? this.multipartBuilder : new MultipartBodyBuilder());
|
||||
this.multipartBuilder.part(name, part);
|
||||
this.parts = (this.parts != null ? this.parts : new LinkedMultiValueMap<>());
|
||||
this.parts.add(name, part);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #addRequestPart(String, Object)} that allows the
|
||||
* part value to be produced by a {@link Publisher}.
|
||||
* @deprecated in favor of {@link ReactiveHttpRequestValues.Builder#addRequestPartPublisher};
|
||||
* to be removed in 6.2
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public <T, P extends Publisher<T>> Builder addRequestPart(String name, P publisher, ResolvableType type) {
|
||||
this.multipartBuilder = (this.multipartBuilder != null ? this.multipartBuilder : new MultipartBodyBuilder());
|
||||
this.multipartBuilder.asyncPart(name, publisher, ParameterizedTypeReference.forType(type.getType()));
|
||||
return this;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -358,25 +354,22 @@ public final class HttpRequestValues {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the request body as a concrete value to be serialized.
|
||||
* <p>This is mutually exclusive with, and resets any previously set
|
||||
* {@linkplain #setBody(Publisher, ParameterizedTypeReference) body Publisher}.
|
||||
* Set the request body as an Object to be serialized.
|
||||
*/
|
||||
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.
|
||||
* Set the request body as a Reactive Streams Publisher.
|
||||
* <p>This is mutually exclusive with, and resets any previously set
|
||||
* {@linkplain #setBodyValue(Object) body value}.
|
||||
* @deprecated in favor of {@link ReactiveHttpRequestValues.Builder#setBodyPublisher};
|
||||
* to be removed in 6.2
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public <T, P extends Publisher<T>> void setBody(P body, ParameterizedTypeReference<T> elementTye) {
|
||||
this.body = body;
|
||||
this.bodyElementType = elementTye;
|
||||
this.bodyValue = null;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -389,15 +382,15 @@ public final class HttpRequestValues {
|
|||
Map<String, String> uriVars = (this.uriVars != null ? new HashMap<>(this.uriVars) : Collections.emptyMap());
|
||||
|
||||
Object bodyValue = this.bodyValue;
|
||||
if (this.multipartBuilder != null) {
|
||||
Assert.isTrue(bodyValue == null && this.body == null, "Expected body or request parts, not both");
|
||||
bodyValue = this.multipartBuilder.build();
|
||||
if (hasParts()) {
|
||||
Assert.isTrue(!hasBody(), "Expected body or request parts, not both");
|
||||
bodyValue = buildMultipartBody();
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.requestParams)) {
|
||||
if (hasContentType(MediaType.APPLICATION_FORM_URLENCODED)) {
|
||||
Assert.isTrue(this.multipartBuilder == null, "Cannot add parts to form data request");
|
||||
Assert.isTrue(bodyValue == null && this.body == null, "Cannot set body of form data request");
|
||||
if (hasFormDataContentType()) {
|
||||
Assert.isTrue(!hasParts(), "Request parts not expected for a form data request");
|
||||
Assert.isTrue(!hasBody(), "Body not expected for a form data request");
|
||||
bodyValue = new LinkedMultiValueMap<>(this.requestParams);
|
||||
}
|
||||
else if (uri != null) {
|
||||
|
|
@ -426,13 +419,26 @@ public final class HttpRequestValues {
|
|||
Map<String, Object> attributes = (this.attributes != null ?
|
||||
new HashMap<>(this.attributes) : Collections.emptyMap());
|
||||
|
||||
return new HttpRequestValues(
|
||||
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes,
|
||||
bodyValue, this.body, this.bodyElementType);
|
||||
return createRequestValues(
|
||||
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes, bodyValue);
|
||||
}
|
||||
|
||||
private boolean hasContentType(MediaType mediaType) {
|
||||
return (this.headers != null && mediaType.equals(this.headers.getContentType()));
|
||||
protected boolean hasParts() {
|
||||
return (this.parts != null);
|
||||
}
|
||||
|
||||
protected boolean hasBody() {
|
||||
return (this.bodyValue != null);
|
||||
}
|
||||
|
||||
protected Object buildMultipartBody() {
|
||||
Assert.notNull(this.parts, "`parts` is null, was hasParts() not called?");
|
||||
return this.parts;
|
||||
}
|
||||
|
||||
private boolean hasFormDataContentType() {
|
||||
return (this.headers != null &&
|
||||
MediaType.APPLICATION_FORM_URLENCODED.equals(this.headers.getContentType()));
|
||||
}
|
||||
|
||||
private String appendQueryParams(
|
||||
|
|
@ -453,6 +459,15 @@ public final class HttpRequestValues {
|
|||
return uriComponentsBuilder.build().toUriString();
|
||||
}
|
||||
|
||||
protected HttpRequestValues createRequestValues(
|
||||
@Nullable HttpMethod httpMethod,
|
||||
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVars,
|
||||
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
|
||||
@Nullable Object bodyValue) {
|
||||
|
||||
return new HttpRequestValues(
|
||||
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes, bodyValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
|
@ -81,13 +82,16 @@ final class HttpServiceMethod {
|
|||
this.parameters = initMethodParameters(method);
|
||||
this.argumentResolvers = argumentResolvers;
|
||||
|
||||
this.requestValuesInitializer =
|
||||
HttpRequestValuesInitializer.create(method, containingClass, embeddedValueResolver);
|
||||
boolean isReactorAdapter = (REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter);
|
||||
|
||||
this.responseFunction =
|
||||
(REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter reactorAdapter ?
|
||||
ReactorExchangeResponseFunction.create(reactorAdapter, method) :
|
||||
ExchangeResponseFunction.create(adapter, method));
|
||||
this.requestValuesInitializer =
|
||||
HttpRequestValuesInitializer.create(
|
||||
method, containingClass, embeddedValueResolver,
|
||||
(isReactorAdapter ? ReactiveHttpRequestValues::builder : HttpRequestValues::builder));
|
||||
|
||||
this.responseFunction = (isReactorAdapter ?
|
||||
ReactorExchangeResponseFunction.create((ReactorHttpExchangeAdapter) adapter, method) :
|
||||
ExchangeResponseFunction.create(adapter, method));
|
||||
}
|
||||
|
||||
private static MethodParameter[] initMethodParameters(Method method) {
|
||||
|
|
@ -147,20 +151,11 @@ final class HttpServiceMethod {
|
|||
*/
|
||||
private record HttpRequestValuesInitializer(
|
||||
@Nullable HttpMethod httpMethod, @Nullable String url,
|
||||
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
|
||||
|
||||
private HttpRequestValuesInitializer(
|
||||
HttpMethod httpMethod, @Nullable String url,
|
||||
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
|
||||
|
||||
this.url = url;
|
||||
this.httpMethod = httpMethod;
|
||||
this.contentType = contentType;
|
||||
this.acceptMediaTypes = acceptMediaTypes;
|
||||
}
|
||||
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes,
|
||||
Supplier<HttpRequestValues.Builder> requestValuesSupplier) {
|
||||
|
||||
public HttpRequestValues.Builder initializeRequestValuesBuilder() {
|
||||
HttpRequestValues.Builder requestValues = HttpRequestValues.builder();
|
||||
HttpRequestValues.Builder requestValues = this.requestValuesSupplier.get();
|
||||
if (this.httpMethod != null) {
|
||||
requestValues.setHttpMethod(this.httpMethod);
|
||||
}
|
||||
|
|
@ -181,7 +176,8 @@ final class HttpServiceMethod {
|
|||
* Introspect the method and create the request factory for it.
|
||||
*/
|
||||
public static HttpRequestValuesInitializer create(
|
||||
Method method, Class<?> containingClass, @Nullable StringValueResolver embeddedValueResolver) {
|
||||
Method method, Class<?> containingClass, @Nullable StringValueResolver embeddedValueResolver,
|
||||
Supplier<HttpRequestValues.Builder> requestValuesSupplier) {
|
||||
|
||||
HttpExchange annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class);
|
||||
HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class);
|
||||
|
|
@ -193,7 +189,8 @@ final class HttpServiceMethod {
|
|||
MediaType contentType = initContentType(annot1, annot2);
|
||||
List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2);
|
||||
|
||||
return new HttpRequestValuesInitializer(httpMethod, url, contentType, acceptableMediaTypes);
|
||||
return new HttpRequestValuesInitializer(
|
||||
httpMethod, url, contentType, acceptableMediaTypes, requestValuesSupplier);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* Copyright 2002-2023 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.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.MultipartBodyBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* {@link HttpRequestValues} extension for use with {@link ReactorHttpExchangeAdapter}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.1
|
||||
*/
|
||||
public final class ReactiveHttpRequestValues extends HttpRequestValues {
|
||||
|
||||
@Nullable
|
||||
private final Publisher<?> body;
|
||||
|
||||
@Nullable
|
||||
private final ParameterizedTypeReference<?> bodyElementType;
|
||||
|
||||
|
||||
private ReactiveHttpRequestValues(
|
||||
@Nullable HttpMethod httpMethod,
|
||||
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
|
||||
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
|
||||
@Nullable Object bodyValue, @Nullable Publisher<?> body, @Nullable ParameterizedTypeReference<?> elementType) {
|
||||
|
||||
super(httpMethod, uri, uriTemplate, uriVariables, headers, cookies, attributes, bodyValue);
|
||||
|
||||
this.body = body;
|
||||
this.bodyElementType = elementType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a {@link Publisher} that will produce for the request body.
|
||||
* <p>This is mutually exclusive with {@link #getBodyValue()}.
|
||||
* Only one of the two or neither is set.
|
||||
*/
|
||||
@Nullable
|
||||
public Publisher<?> getBodyPublisher() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the element type for a {@linkplain #getBodyPublisher() Publisher body}.
|
||||
*/
|
||||
@Nullable
|
||||
public ParameterizedTypeReference<?> getBodyPublisherElementType() {
|
||||
return this.bodyElementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the request body as a Publisher.
|
||||
* <p>This is mutually exclusive with {@link #getBodyValue()}.
|
||||
* Only one of the two or neither is set.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Nullable
|
||||
public Publisher<?> getBody() {
|
||||
return getBodyPublisher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the element type for a {@linkplain #getBodyPublisher() Publisher body}.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Nullable
|
||||
public ParameterizedTypeReference<?> getBodyElementType() {
|
||||
return getBodyPublisherElementType();
|
||||
}
|
||||
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link ReactiveHttpRequestValues}.
|
||||
*/
|
||||
public final static class Builder extends HttpRequestValues.Builder {
|
||||
|
||||
@Nullable
|
||||
private MultipartBodyBuilder multipartBuilder;
|
||||
|
||||
@Nullable
|
||||
private Publisher<?> body;
|
||||
|
||||
@Nullable
|
||||
private ParameterizedTypeReference<?> bodyElementType;
|
||||
|
||||
@Override
|
||||
public Builder setHttpMethod(HttpMethod httpMethod) {
|
||||
super.setHttpMethod(httpMethod);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setUri(URI uri) {
|
||||
super.setUri(uri);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setUriTemplate(String uriTemplate) {
|
||||
super.setUriTemplate(uriTemplate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setUriVariable(String name, String value) {
|
||||
super.setUriVariable(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setAccept(List<MediaType> acceptableMediaTypes) {
|
||||
super.setAccept(acceptableMediaTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setContentType(MediaType contentType) {
|
||||
super.setContentType(contentType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addHeader(String headerName, String... headerValues) {
|
||||
super.addHeader(headerName, headerValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addCookie(String name, String... values) {
|
||||
super.addCookie(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addRequestParameter(String name, String... values) {
|
||||
super.addRequestParameter(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addAttribute(String name, Object value) {
|
||||
super.addAttribute(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a part to a multipart request. The part value may be as described
|
||||
* in {@link MultipartBodyBuilder#part(String, Object)}.
|
||||
*/
|
||||
@Override
|
||||
public Builder addRequestPart(String name, Object part) {
|
||||
this.multipartBuilder = (this.multipartBuilder != null ? this.multipartBuilder : new MultipartBodyBuilder());
|
||||
this.multipartBuilder.part(name, part);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #addRequestPart(String, Object)} that allows the
|
||||
* part value to be produced by a {@link Publisher}.
|
||||
*/
|
||||
public <T, P extends Publisher<T>> Builder addRequestPartPublisher(
|
||||
String name, P publisher, ParameterizedTypeReference<T> elementTye) {
|
||||
|
||||
this.multipartBuilder = (this.multipartBuilder != null ? this.multipartBuilder : new MultipartBodyBuilder());
|
||||
this.multipartBuilder.asyncPart(name, publisher, elementTye);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@Override
|
||||
public <T, P extends Publisher<T>> Builder addRequestPart(String name, P publisher, ResolvableType type) {
|
||||
return addRequestPartPublisher(name, publisher, ParameterizedTypeReference.forType(type.getType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This is mutually exclusive with, and resets any previously set
|
||||
* {@linkplain #setBodyPublisher(Publisher, ParameterizedTypeReference)}.
|
||||
*/
|
||||
@Override
|
||||
public void setBodyValue(Object bodyValue) {
|
||||
super.setBodyValue(bodyValue);
|
||||
this.body = null;
|
||||
this.bodyElementType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request body as a Reactive Streams Publisher.
|
||||
* <p>This is mutually exclusive with, and resets any previously set
|
||||
* {@linkplain #setBodyValue(Object) body value}.
|
||||
*/
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
public <T, P extends Publisher<T>> void setBodyPublisher(P body, ParameterizedTypeReference<T> elementTye) {
|
||||
this.body = body;
|
||||
this.bodyElementType = elementTye;
|
||||
super.setBodyValue(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@Override
|
||||
public <T, P extends Publisher<T>> void setBody(P body, ParameterizedTypeReference<T> elementTye) {
|
||||
setBodyPublisher(body, elementTye);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveHttpRequestValues build() {
|
||||
return (ReactiveHttpRequestValues) super.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasParts() {
|
||||
return (this.multipartBuilder != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasBody() {
|
||||
return (super.hasBody() || this.body != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object buildMultipartBody() {
|
||||
Assert.notNull(this.multipartBuilder, "`multipartBuilder` is null, was hasParts() not called?");
|
||||
return this.multipartBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactiveHttpRequestValues createRequestValues(
|
||||
@Nullable HttpMethod httpMethod,
|
||||
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVars,
|
||||
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
|
||||
@Nullable Object bodyValue) {
|
||||
|
||||
return new ReactiveHttpRequestValues(
|
||||
httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes,
|
||||
bodyValue, this.body, this.bodyElementType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -78,9 +78,14 @@ public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver
|
|||
Assert.isTrue(!adapter.isNoValue(), message);
|
||||
Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
|
||||
|
||||
requestValues.setBody(
|
||||
adapter.toPublisher(argument),
|
||||
ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
|
||||
if (requestValues instanceof ReactiveHttpRequestValues.Builder reactiveRequestValues) {
|
||||
reactiveRequestValues.setBodyPublisher(
|
||||
adapter.toPublisher(argument), asParameterizedTypeRef(nestedParameter));
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"RequestBody with a reactive type is only supported with reactive client");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -93,4 +98,8 @@ public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver
|
|||
return true;
|
||||
}
|
||||
|
||||
private static ParameterizedTypeReference<Object> asParameterizedTypeRef(MethodParameter nestedParam) {
|
||||
return ParameterizedTypeReference.forType(nestedParam.getNestedGenericParameterType());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ package org.springframework.web.service.invoker;
|
|||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
|
@ -89,9 +89,20 @@ public class RequestPartArgumentResolver extends AbstractNamedValueArgumentResol
|
|||
Class<?> type = parameter.getParameterType();
|
||||
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(type);
|
||||
if (adapter != null) {
|
||||
Assert.isTrue(!adapter.isNoValue(), "Expected publisher that produces a value");
|
||||
Publisher<?> publisher = adapter.toPublisher(value);
|
||||
requestValues.addRequestPart(name, publisher, ResolvableType.forMethodParameter(parameter.nested()));
|
||||
MethodParameter nestedParameter = parameter.nested();
|
||||
|
||||
String message = "Async type for @RequestPart should produce value(s)";
|
||||
Assert.isTrue(!adapter.isNoValue(), message);
|
||||
Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
|
||||
|
||||
if (requestValues instanceof ReactiveHttpRequestValues.Builder reactiveValues) {
|
||||
reactiveValues.addRequestPartPublisher(
|
||||
name, adapter.toPublisher(value), asParameterizedTypeRef(nestedParameter));
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"RequestPart with a reactive type is only supported with reactive client");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -99,4 +110,8 @@ public class RequestPartArgumentResolver extends AbstractNamedValueArgumentResol
|
|||
requestValues.addRequestPart(name, value);
|
||||
}
|
||||
|
||||
private static ParameterizedTypeReference<Object> asParameterizedTypeRef(MethodParameter nestedParam) {
|
||||
return ParameterizedTypeReference.forType(nestedParam.getNestedGenericParameterType());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,9 +125,9 @@ class HttpRequestValuesTests {
|
|||
.build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MultiValueMap<String, HttpEntity<?>> map = (MultiValueMap<String, HttpEntity<?>>) requestValues.getBodyValue();
|
||||
MultiValueMap<String, Object> map = (MultiValueMap<String, Object>) requestValues.getBodyValue();
|
||||
assertThat(map).hasSize(2);
|
||||
assertThat(map.getFirst("form field").getBody()).isEqualTo("form value");
|
||||
assertThat(map.getFirst("form field")).isEqualTo("form value");
|
||||
assertThat(map.getFirst("entity")).isEqualTo(entity);
|
||||
}
|
||||
|
||||
|
|
@ -146,9 +146,9 @@ class HttpRequestValuesTests {
|
|||
assertThat(uriTemplate).isEqualTo("/path?{queryParam0}={queryParam0[0]}");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MultiValueMap<String, HttpEntity<?>> map = (MultiValueMap<String, HttpEntity<?>>) requestValues.getBodyValue();
|
||||
MultiValueMap<String, Object> map = (MultiValueMap<String, Object>) requestValues.getBodyValue();
|
||||
assertThat(map).hasSize(1);
|
||||
assertThat(map.getFirst("form field").getBody()).isEqualTo("form value");
|
||||
assertThat(map.getFirst("form field")).isEqualTo("form value");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import org.reactivestreams.Publisher;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
|
||||
|
|
@ -32,12 +33,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
|||
|
||||
/**
|
||||
* Unit tests for {@link RequestBodyArgumentResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
class RequestBodyArgumentResolverTests {
|
||||
|
||||
private final TestExchangeAdapter client = new TestExchangeAdapter();
|
||||
private final TestReactorExchangeAdapter client = new TestReactorExchangeAdapter();
|
||||
|
||||
private final Service service =
|
||||
HttpServiceProxyFactory.builderFor(this.client).build().createClient(Service.class);
|
||||
|
|
@ -48,8 +48,8 @@ class RequestBodyArgumentResolverTests {
|
|||
String body = "bodyValue";
|
||||
this.service.execute(body);
|
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isEqualTo(body);
|
||||
assertThat(getRequestValues().getBody()).isNull();
|
||||
assertThat(getBodyValue()).isEqualTo(body);
|
||||
assertThat(getPublisherBody()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -57,9 +57,9 @@ class RequestBodyArgumentResolverTests {
|
|||
Mono<String> bodyMono = Mono.just("bodyValue");
|
||||
this.service.executeMono(bodyMono);
|
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull();
|
||||
assertThat(getRequestValues().getBody()).isSameAs(bodyMono);
|
||||
assertThat(getRequestValues().getBodyElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
|
||||
assertThat(getBodyValue()).isNull();
|
||||
assertThat(getPublisherBody()).isSameAs(bodyMono);
|
||||
assertThat(getBodyElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -68,10 +68,10 @@ class RequestBodyArgumentResolverTests {
|
|||
String bodyValue = "bodyValue";
|
||||
this.service.executeSingle(Single.just(bodyValue));
|
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull();
|
||||
assertThat(getRequestValues().getBodyElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
|
||||
assertThat(getBodyValue()).isNull();
|
||||
assertThat(getBodyElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
|
||||
|
||||
Publisher<?> body = getRequestValues().getBody();
|
||||
Publisher<?> body = getPublisherBody();
|
||||
assertThat(body).isNotNull();
|
||||
assertThat(((Mono<String>) body).block()).isEqualTo(bodyValue);
|
||||
}
|
||||
|
|
@ -104,17 +104,32 @@ class RequestBodyArgumentResolverTests {
|
|||
void ignoreNull() {
|
||||
this.service.execute(null);
|
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull();
|
||||
assertThat(getRequestValues().getBody()).isNull();
|
||||
assertThat(getBodyValue()).isNull();
|
||||
assertThat(getPublisherBody()).isNull();
|
||||
|
||||
this.service.executeMono(null);
|
||||
|
||||
assertThat(getRequestValues().getBodyValue()).isNull();
|
||||
assertThat(getRequestValues().getBody()).isNull();
|
||||
assertThat(getBodyValue()).isNull();
|
||||
assertThat(getPublisherBody()).isNull();
|
||||
}
|
||||
|
||||
private HttpRequestValues getRequestValues() {
|
||||
return this.client.getRequestValues();
|
||||
@Nullable
|
||||
private Object getBodyValue() {
|
||||
return getReactiveRequestValues().getBodyValue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Publisher<?> getPublisherBody() {
|
||||
return getReactiveRequestValues().getBodyPublisher();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ParameterizedTypeReference<?> getBodyElementType() {
|
||||
return getReactiveRequestValues().getBodyPublisherElementType();
|
||||
}
|
||||
|
||||
private ReactiveHttpRequestValues getReactiveRequestValues() {
|
||||
return (ReactiveHttpRequestValues) this.client.getRequestValues();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*/
|
||||
class RequestPartArgumentResolverTests {
|
||||
|
||||
private final TestExchangeAdapter client = new TestExchangeAdapter();
|
||||
private final TestReactorExchangeAdapter client = new TestReactorExchangeAdapter();
|
||||
|
||||
private final Service service =
|
||||
HttpServiceProxyFactory.builderFor(this.client).build().createClient(Service.class);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.reactive.function.client.support;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ import org.springframework.web.reactive.function.client.WebClient;
|
|||
import org.springframework.web.service.invoker.AbstractReactorHttpExchangeAdapter;
|
||||
import org.springframework.web.service.invoker.HttpRequestValues;
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
import org.springframework.web.service.invoker.ReactiveHttpRequestValues;
|
||||
import org.springframework.web.service.invoker.ReactorHttpExchangeAdapter;
|
||||
|
||||
/**
|
||||
|
|
@ -119,9 +121,13 @@ public final class WebClientAdapter extends AbstractReactorHttpExchangeAdapter {
|
|||
if (requestValues.getBodyValue() != null) {
|
||||
bodySpec.bodyValue(requestValues.getBodyValue());
|
||||
}
|
||||
else if (requestValues.getBody() != null) {
|
||||
Assert.notNull(requestValues.getBodyElementType(), "Publisher body element type is required");
|
||||
bodySpec.body(requestValues.getBody(), requestValues.getBodyElementType());
|
||||
else if (requestValues instanceof ReactiveHttpRequestValues reactiveRequestValues) {
|
||||
Publisher<?> body = reactiveRequestValues.getBodyPublisher();
|
||||
if (body != null) {
|
||||
ParameterizedTypeReference<?> elementType = reactiveRequestValues.getBodyPublisherElementType();
|
||||
Assert.notNull(elementType, "Publisher body element type is required");
|
||||
bodySpec.body(body, elementType);
|
||||
}
|
||||
}
|
||||
|
||||
return bodySpec;
|
||||
|
|
|
|||
Loading…
Reference in New Issue