Skip to content

Commit a3e3759

Browse files
committed
Add ReactiveHttpRequestValues
Separate collection and handling of reactive request values into a subclass of HttpRequestValues. Closes gh-30117
1 parent 3209cf5 commit a3e3759

File tree

9 files changed

+435
-103
lines changed

9 files changed

+435
-103
lines changed

spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java

+68-53
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727

2828
import org.springframework.core.ParameterizedTypeReference;
2929
import org.springframework.core.ResolvableType;
30+
import org.springframework.http.HttpEntity;
3031
import org.springframework.http.HttpHeaders;
3132
import org.springframework.http.HttpMethod;
3233
import org.springframework.http.MediaType;
33-
import org.springframework.http.client.MultipartBodyBuilder;
3434
import org.springframework.lang.Nullable;
3535
import org.springframework.util.Assert;
3636
import org.springframework.util.CollectionUtils;
@@ -48,7 +48,7 @@
4848
* @author Rossen Stoyanchev
4949
* @since 6.0
5050
*/
51-
public final class HttpRequestValues {
51+
public class HttpRequestValues {
5252

5353
private static final MultiValueMap<String, String> EMPTY_COOKIES_MAP =
5454
CollectionUtils.toMultiValueMap(Collections.emptyMap());
@@ -74,18 +74,11 @@ public final class HttpRequestValues {
7474
@Nullable
7575
private final Object bodyValue;
7676

77-
@Nullable
78-
private final Publisher<?> body;
79-
80-
@Nullable
81-
private final ParameterizedTypeReference<?> bodyElementType;
8277

83-
84-
private HttpRequestValues(@Nullable HttpMethod httpMethod,
78+
protected HttpRequestValues(@Nullable HttpMethod httpMethod,
8579
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
8680
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
87-
@Nullable Object bodyValue,
88-
@Nullable Publisher<?> body, @Nullable ParameterizedTypeReference<?> bodyElementType) {
81+
@Nullable Object bodyValue) {
8982

9083
Assert.isTrue(uri != null || uriTemplate != null, "Neither URI nor URI template");
9184

@@ -97,8 +90,6 @@ private HttpRequestValues(@Nullable HttpMethod httpMethod,
9790
this.cookies = cookies;
9891
this.attributes = attributes;
9992
this.bodyValue = bodyValue;
100-
this.body = body;
101-
this.bodyElementType = bodyElementType;
10293
}
10394

10495

@@ -161,8 +152,6 @@ public Map<String, Object> getAttributes() {
161152

162153
/**
163154
* Return the request body as a value to be serialized, if set.
164-
* <p>This is mutually exclusive with {@link #getBody()}.
165-
* Only one of the two or neither is set.
166155
*/
167156
@Nullable
168157
public Object getBodyValue() {
@@ -173,18 +162,24 @@ public Object getBodyValue() {
173162
* Return the request body as a Publisher.
174163
* <p>This is mutually exclusive with {@link #getBodyValue()}.
175164
* Only one of the two or neither is set.
165+
* @deprecated in favor of {@link ReactiveHttpRequestValues#getBodyPublisher()};
166+
* to be removed in 6.2
176167
*/
168+
@Deprecated(since = "6.1", forRemoval = true)
177169
@Nullable
178170
public Publisher<?> getBody() {
179-
return this.body;
171+
throw new UnsupportedOperationException();
180172
}
181173

182174
/**
183-
* Return the element type for a {@linkplain #getBody() Publisher body}.
175+
* Return the element type for a Publisher body.
176+
* @deprecated in favor of {@link ReactiveHttpRequestValues#getBodyPublisherElementType()};
177+
* to be removed in 6.2
184178
*/
179+
@Deprecated(since = "6.1", forRemoval = true)
185180
@Nullable
186181
public ParameterizedTypeReference<?> getBodyElementType() {
187-
return this.bodyElementType;
182+
throw new UnsupportedOperationException();
188183
}
189184

190185

@@ -196,7 +191,7 @@ public static Builder builder() {
196191
/**
197192
* Builder for {@link HttpRequestValues}.
198193
*/
199-
public final static class Builder {
194+
public static class Builder {
200195

201196
@Nullable
202197
private HttpMethod httpMethod;
@@ -220,20 +215,14 @@ public final static class Builder {
220215
private MultiValueMap<String, String> requestParams;
221216

222217
@Nullable
223-
private MultipartBodyBuilder multipartBuilder;
218+
private MultiValueMap<String, Object> parts;
224219

225220
@Nullable
226221
private Map<String, Object> attributes;
227222

228223
@Nullable
229224
private Object bodyValue;
230225

231-
@Nullable
232-
private Publisher<?> body;
233-
234-
@Nullable
235-
private ParameterizedTypeReference<?> bodyElementType;
236-
237226
/**
238227
* Set the HTTP method for the request.
239228
*/
@@ -327,23 +316,30 @@ public Builder addRequestParameter(String name, String... values) {
327316
}
328317

329318
/**
330-
* Add a part to a multipart request. The part value may be as described
331-
* in {@link MultipartBodyBuilder#part(String, Object)}.
319+
* Add a part for a multipart request. The part may be:
320+
* <ul>
321+
* <li>String -- form field
322+
* <li>{@link org.springframework.core.io.Resource Resource} -- file part
323+
* <li>Object -- content to be encoded (e.g. to JSON)
324+
* <li>{@link HttpEntity} -- part content and headers although generally it's
325+
* easier to add headers through the returned builder
326+
* </ul>
332327
*/
333328
public Builder addRequestPart(String name, Object part) {
334-
this.multipartBuilder = (this.multipartBuilder != null ? this.multipartBuilder : new MultipartBodyBuilder());
335-
this.multipartBuilder.part(name, part);
329+
this.parts = (this.parts != null ? this.parts : new LinkedMultiValueMap<>());
330+
this.parts.add(name, part);
336331
return this;
337332
}
338333

339334
/**
340335
* Variant of {@link #addRequestPart(String, Object)} that allows the
341336
* part value to be produced by a {@link Publisher}.
337+
* @deprecated in favor of {@link ReactiveHttpRequestValues.Builder#addRequestPartPublisher};
338+
* to be removed in 6.2
342339
*/
340+
@Deprecated(since = "6.1", forRemoval = true)
343341
public <T, P extends Publisher<T>> Builder addRequestPart(String name, P publisher, ResolvableType type) {
344-
this.multipartBuilder = (this.multipartBuilder != null ? this.multipartBuilder : new MultipartBodyBuilder());
345-
this.multipartBuilder.asyncPart(name, publisher, ParameterizedTypeReference.forType(type.getType()));
346-
return this;
342+
throw new UnsupportedOperationException();
347343
}
348344

349345
/**
@@ -358,25 +354,22 @@ public Builder addAttribute(String name, Object value) {
358354
}
359355

360356
/**
361-
* Set the request body as a concrete value to be serialized.
362-
* <p>This is mutually exclusive with, and resets any previously set
363-
* {@linkplain #setBody(Publisher, ParameterizedTypeReference) body Publisher}.
357+
* Set the request body as an Object to be serialized.
364358
*/
365359
public void setBodyValue(Object bodyValue) {
366360
this.bodyValue = bodyValue;
367-
this.body = null;
368-
this.bodyElementType = null;
369361
}
370362

371363
/**
372-
* Set the request body as a concrete value to be serialized.
364+
* Set the request body as a Reactive Streams Publisher.
373365
* <p>This is mutually exclusive with, and resets any previously set
374366
* {@linkplain #setBodyValue(Object) body value}.
367+
* @deprecated in favor of {@link ReactiveHttpRequestValues.Builder#setBodyPublisher};
368+
* to be removed in 6.2
375369
*/
370+
@Deprecated(since = "6.1", forRemoval = true)
376371
public <T, P extends Publisher<T>> void setBody(P body, ParameterizedTypeReference<T> elementTye) {
377-
this.body = body;
378-
this.bodyElementType = elementTye;
379-
this.bodyValue = null;
372+
throw new UnsupportedOperationException();
380373
}
381374

382375
/**
@@ -389,15 +382,15 @@ public HttpRequestValues build() {
389382
Map<String, String> uriVars = (this.uriVars != null ? new HashMap<>(this.uriVars) : Collections.emptyMap());
390383

391384
Object bodyValue = this.bodyValue;
392-
if (this.multipartBuilder != null) {
393-
Assert.isTrue(bodyValue == null && this.body == null, "Expected body or request parts, not both");
394-
bodyValue = this.multipartBuilder.build();
385+
if (hasParts()) {
386+
Assert.isTrue(!hasBody(), "Expected body or request parts, not both");
387+
bodyValue = buildMultipartBody();
395388
}
396389

397390
if (!CollectionUtils.isEmpty(this.requestParams)) {
398-
if (hasContentType(MediaType.APPLICATION_FORM_URLENCODED)) {
399-
Assert.isTrue(this.multipartBuilder == null, "Cannot add parts to form data request");
400-
Assert.isTrue(bodyValue == null && this.body == null, "Cannot set body of form data request");
391+
if (hasFormDataContentType()) {
392+
Assert.isTrue(!hasParts(), "Request parts not expected for a form data request");
393+
Assert.isTrue(!hasBody(), "Body not expected for a form data request");
401394
bodyValue = new LinkedMultiValueMap<>(this.requestParams);
402395
}
403396
else if (uri != null) {
@@ -426,13 +419,26 @@ else if (uri != null) {
426419
Map<String, Object> attributes = (this.attributes != null ?
427420
new HashMap<>(this.attributes) : Collections.emptyMap());
428421

429-
return new HttpRequestValues(
430-
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes,
431-
bodyValue, this.body, this.bodyElementType);
422+
return createRequestValues(
423+
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes, bodyValue);
424+
}
425+
426+
protected boolean hasParts() {
427+
return (this.parts != null);
428+
}
429+
430+
protected boolean hasBody() {
431+
return (this.bodyValue != null);
432432
}
433433

434-
private boolean hasContentType(MediaType mediaType) {
435-
return (this.headers != null && mediaType.equals(this.headers.getContentType()));
434+
protected Object buildMultipartBody() {
435+
Assert.notNull(this.parts, "`parts` is null, was hasParts() not called?");
436+
return this.parts;
437+
}
438+
439+
private boolean hasFormDataContentType() {
440+
return (this.headers != null &&
441+
MediaType.APPLICATION_FORM_URLENCODED.equals(this.headers.getContentType()));
436442
}
437443

438444
private String appendQueryParams(
@@ -453,6 +459,15 @@ private String appendQueryParams(
453459
return uriComponentsBuilder.build().toUriString();
454460
}
455461

462+
protected HttpRequestValues createRequestValues(
463+
@Nullable HttpMethod httpMethod,
464+
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVars,
465+
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
466+
@Nullable Object bodyValue) {
467+
468+
return new HttpRequestValues(
469+
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes, bodyValue);
470+
}
456471
}
457472

458473
}

spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java

+16-19
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Optional;
2424
import java.util.function.Function;
25+
import java.util.function.Supplier;
2526

2627
import org.reactivestreams.Publisher;
2728
import reactor.core.publisher.Flux;
@@ -81,13 +82,16 @@ final class HttpServiceMethod {
8182
this.parameters = initMethodParameters(method);
8283
this.argumentResolvers = argumentResolvers;
8384

85+
boolean isReactorAdapter = (REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter);
86+
8487
this.requestValuesInitializer =
85-
HttpRequestValuesInitializer.create(method, containingClass, embeddedValueResolver);
88+
HttpRequestValuesInitializer.create(
89+
method, containingClass, embeddedValueResolver,
90+
(isReactorAdapter ? ReactiveHttpRequestValues::builder : HttpRequestValues::builder));
8691

87-
this.responseFunction =
88-
(REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter reactorAdapter ?
89-
ReactorExchangeResponseFunction.create(reactorAdapter, method) :
90-
ExchangeResponseFunction.create(adapter, method));
92+
this.responseFunction = (isReactorAdapter ?
93+
ReactorExchangeResponseFunction.create((ReactorHttpExchangeAdapter) adapter, method) :
94+
ExchangeResponseFunction.create(adapter, method));
9195
}
9296

9397
private static MethodParameter[] initMethodParameters(Method method) {
@@ -147,20 +151,11 @@ private void applyArguments(HttpRequestValues.Builder requestValues, Object[] ar
147151
*/
148152
private record HttpRequestValuesInitializer(
149153
@Nullable HttpMethod httpMethod, @Nullable String url,
150-
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
151-
152-
private HttpRequestValuesInitializer(
153-
HttpMethod httpMethod, @Nullable String url,
154-
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes) {
155-
156-
this.url = url;
157-
this.httpMethod = httpMethod;
158-
this.contentType = contentType;
159-
this.acceptMediaTypes = acceptMediaTypes;
160-
}
154+
@Nullable MediaType contentType, @Nullable List<MediaType> acceptMediaTypes,
155+
Supplier<HttpRequestValues.Builder> requestValuesSupplier) {
161156

162157
public HttpRequestValues.Builder initializeRequestValuesBuilder() {
163-
HttpRequestValues.Builder requestValues = HttpRequestValues.builder();
158+
HttpRequestValues.Builder requestValues = this.requestValuesSupplier.get();
164159
if (this.httpMethod != null) {
165160
requestValues.setHttpMethod(this.httpMethod);
166161
}
@@ -181,7 +176,8 @@ public HttpRequestValues.Builder initializeRequestValuesBuilder() {
181176
* Introspect the method and create the request factory for it.
182177
*/
183178
public static HttpRequestValuesInitializer create(
184-
Method method, Class<?> containingClass, @Nullable StringValueResolver embeddedValueResolver) {
179+
Method method, Class<?> containingClass, @Nullable StringValueResolver embeddedValueResolver,
180+
Supplier<HttpRequestValues.Builder> requestValuesSupplier) {
185181

186182
HttpExchange annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class);
187183
HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class);
@@ -193,7 +189,8 @@ public static HttpRequestValuesInitializer create(
193189
MediaType contentType = initContentType(annot1, annot2);
194190
List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2);
195191

196-
return new HttpRequestValuesInitializer(httpMethod, url, contentType, acceptableMediaTypes);
192+
return new HttpRequestValuesInitializer(
193+
httpMethod, url, contentType, acceptableMediaTypes, requestValuesSupplier);
197194
}
198195

199196
@Nullable

0 commit comments

Comments
 (0)