Skip to content

Commit 6f3051c

Browse files
committed
Support for @RequestPart with reactive type wrapper
Issue: SPR-14546
1 parent fc7bede commit 6f3051c

File tree

3 files changed

+34
-38
lines changed

3 files changed

+34
-38
lines changed

spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@
4848
import org.springframework.web.server.WebSession;
4949
import org.springframework.web.server.session.WebSessionManager;
5050

51-
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
52-
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
53-
5451
/**
5552
* Default implementation of {@link ServerWebExchange}.
5653
*
@@ -61,10 +58,10 @@ public class DefaultServerWebExchange implements ServerWebExchange {
6158

6259
private static final List<HttpMethod> SAFE_METHODS = Arrays.asList(HttpMethod.GET, HttpMethod.HEAD);
6360

64-
private static final ResolvableType FORM_DATA_VALUE_TYPE =
61+
private static final ResolvableType FORM_DATA_TYPE =
6562
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
6663

67-
private static final ResolvableType MULTIPART_VALUE_TYPE = ResolvableType.forClassWithGenerics(
64+
private static final ResolvableType MULTIPART_DATA_TYPE = ResolvableType.forClassWithGenerics(
6865
MultiValueMap.class, String.class, Part.class);
6966

7067
private static final Mono<MultiValueMap<String, String>> EMPTY_FORM_DATA =
@@ -110,21 +107,17 @@ public DefaultServerWebExchange(ServerHttpRequest request, ServerHttpResponse re
110107
}
111108

112109
@SuppressWarnings("unchecked")
113-
private static Mono<MultiValueMap<String, String>> initFormData(
114-
ServerHttpRequest request, ServerCodecConfigurer codecConfigurer) {
110+
private static Mono<MultiValueMap<String, String>> initFormData(ServerHttpRequest request,
111+
ServerCodecConfigurer configurer) {
115112

116-
MediaType contentType;
117113
try {
118-
contentType = request.getHeaders().getContentType();
119-
if (APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
120-
return ((HttpMessageReader<MultiValueMap<String, String>>)codecConfigurer
121-
.getReaders()
122-
.stream()
123-
.filter(reader -> reader.canRead(FORM_DATA_VALUE_TYPE, APPLICATION_FORM_URLENCODED))
114+
MediaType contentType = request.getHeaders().getContentType();
115+
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
116+
return ((HttpMessageReader<MultiValueMap<String, String>>) configurer.getReaders().stream()
117+
.filter(reader -> reader.canRead(FORM_DATA_TYPE, MediaType.APPLICATION_FORM_URLENCODED))
124118
.findFirst()
125-
.orElseThrow(() -> new IllegalStateException(
126-
"Could not find HttpMessageReader that supports " + APPLICATION_FORM_URLENCODED)))
127-
.readMono(FORM_DATA_VALUE_TYPE, request, Collections.emptyMap())
119+
.orElseThrow(() -> new IllegalStateException("No form data HttpMessageReader.")))
120+
.readMono(FORM_DATA_TYPE, request, Collections.emptyMap())
128121
.switchIfEmpty(EMPTY_FORM_DATA)
129122
.cache();
130123
}
@@ -136,21 +129,17 @@ private static Mono<MultiValueMap<String, String>> initFormData(
136129
}
137130

138131
@SuppressWarnings("unchecked")
139-
private static Mono<MultiValueMap<String, Part>> initMultipartData(
140-
ServerHttpRequest request, ServerCodecConfigurer codecConfigurer) {
132+
private static Mono<MultiValueMap<String, Part>> initMultipartData(ServerHttpRequest request,
133+
ServerCodecConfigurer configurer) {
141134

142-
MediaType contentType;
143135
try {
144-
contentType = request.getHeaders().getContentType();
145-
if (MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
146-
return ((HttpMessageReader<MultiValueMap<String, Part>>) codecConfigurer
147-
.getReaders()
148-
.stream()
149-
.filter(reader -> reader.canRead(MULTIPART_VALUE_TYPE, MULTIPART_FORM_DATA))
136+
MediaType contentType = request.getHeaders().getContentType();
137+
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
138+
return ((HttpMessageReader<MultiValueMap<String, Part>>) configurer.getReaders().stream()
139+
.filter(reader -> reader.canRead(MULTIPART_DATA_TYPE, MediaType.MULTIPART_FORM_DATA))
150140
.findFirst()
151-
.orElseThrow(() -> new IllegalStateException(
152-
"Could not find HttpMessageReader that supports " + MULTIPART_FORM_DATA)))
153-
.readMono(FORM_DATA_VALUE_TYPE, request, Collections.emptyMap())
141+
.orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader.")))
142+
.readMono(MULTIPART_DATA_TYPE, request, Collections.emptyMap())
154143
.switchIfEmpty(EMPTY_MULTIPART_DATA)
155144
.cache();
156145
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import reactor.core.publisher.Mono;
2222

2323
import org.springframework.core.MethodParameter;
24+
import org.springframework.core.ReactiveAdapter;
2425
import org.springframework.core.ReactiveAdapterRegistry;
2526
import org.springframework.http.codec.multipart.Part;
2627
import org.springframework.util.CollectionUtils;
@@ -61,13 +62,16 @@ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
6162

6263
@Override
6364
protected Mono<Object> resolveName(String name, MethodParameter param, ServerWebExchange exchange) {
64-
return exchange.getMultipartData().flatMap(allParts -> {
65-
List<Part> parts = allParts.get(name);
66-
if (CollectionUtils.isEmpty(parts)) {
67-
return Mono.empty();
68-
}
69-
return Mono.just(parts.size() == 1 ? parts.get(0) : parts);
70-
});
65+
66+
Mono<Object> partsMono = exchange.getMultipartData()
67+
.filter(map -> !CollectionUtils.isEmpty(map.get(name)))
68+
.map(map -> {
69+
List<Part> parts = map.get(name);
70+
return parts.size() == 1 ? parts.get(0) : parts;
71+
});
72+
73+
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(param.getParameterType());
74+
return (adapter != null ? Mono.just(adapter.fromPublisher(partsMono)) : partsMono);
7175
}
7276

7377
@Override

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.reactive.result.method.annotation;
1818

19+
import java.time.Duration;
1920
import java.util.Map;
2021
import java.util.stream.Collectors;
2122

@@ -34,6 +35,7 @@
3435
import org.springframework.http.HttpStatus;
3536
import org.springframework.http.MediaType;
3637
import org.springframework.http.codec.multipart.FilePart;
38+
import org.springframework.http.codec.multipart.FormFieldPart;
3739
import org.springframework.http.codec.multipart.Part;
3840
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
3941
import org.springframework.http.server.reactive.HttpHandler;
@@ -152,8 +154,9 @@ private MultiValueMap<String, Object> generateBody() {
152154
static class MultipartController {
153155

154156
@PostMapping("/requestPart")
155-
void requestPart(@RequestPart Part fooPart) {
156-
assertEquals("foo.txt", ((FilePart) fooPart).getFilename());
157+
void requestPart(@RequestPart FormFieldPart barPart, @RequestPart Mono<FilePart> fooPart) {
158+
assertEquals("bar", barPart.getValue());
159+
assertEquals("foo.txt", fooPart.block(Duration.ZERO).getFilename());
157160
}
158161

159162
@PostMapping("/requestBodyMap")

0 commit comments

Comments
 (0)