Support for @RequestPart with reactive type wrapper
Issue: SPR-14546
This commit is contained in:
parent
fc7bededd0
commit
6f3051c677
|
|
@ -48,9 +48,6 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ServerWebExchange}.
|
||||
*
|
||||
|
|
@ -61,10 +58,10 @@ public class DefaultServerWebExchange implements ServerWebExchange {
|
|||
|
||||
private static final List<HttpMethod> SAFE_METHODS = Arrays.asList(HttpMethod.GET, HttpMethod.HEAD);
|
||||
|
||||
private static final ResolvableType FORM_DATA_VALUE_TYPE =
|
||||
private static final ResolvableType FORM_DATA_TYPE =
|
||||
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
|
||||
|
||||
private static final ResolvableType MULTIPART_VALUE_TYPE = ResolvableType.forClassWithGenerics(
|
||||
private static final ResolvableType MULTIPART_DATA_TYPE = ResolvableType.forClassWithGenerics(
|
||||
MultiValueMap.class, String.class, Part.class);
|
||||
|
||||
private static final Mono<MultiValueMap<String, String>> EMPTY_FORM_DATA =
|
||||
|
|
@ -110,21 +107,17 @@ public class DefaultServerWebExchange implements ServerWebExchange {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Mono<MultiValueMap<String, String>> initFormData(
|
||||
ServerHttpRequest request, ServerCodecConfigurer codecConfigurer) {
|
||||
private static Mono<MultiValueMap<String, String>> initFormData(ServerHttpRequest request,
|
||||
ServerCodecConfigurer configurer) {
|
||||
|
||||
MediaType contentType;
|
||||
try {
|
||||
contentType = request.getHeaders().getContentType();
|
||||
if (APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
|
||||
return ((HttpMessageReader<MultiValueMap<String, String>>)codecConfigurer
|
||||
.getReaders()
|
||||
.stream()
|
||||
.filter(reader -> reader.canRead(FORM_DATA_VALUE_TYPE, APPLICATION_FORM_URLENCODED))
|
||||
MediaType contentType = request.getHeaders().getContentType();
|
||||
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
|
||||
return ((HttpMessageReader<MultiValueMap<String, String>>) configurer.getReaders().stream()
|
||||
.filter(reader -> reader.canRead(FORM_DATA_TYPE, MediaType.APPLICATION_FORM_URLENCODED))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"Could not find HttpMessageReader that supports " + APPLICATION_FORM_URLENCODED)))
|
||||
.readMono(FORM_DATA_VALUE_TYPE, request, Collections.emptyMap())
|
||||
.orElseThrow(() -> new IllegalStateException("No form data HttpMessageReader.")))
|
||||
.readMono(FORM_DATA_TYPE, request, Collections.emptyMap())
|
||||
.switchIfEmpty(EMPTY_FORM_DATA)
|
||||
.cache();
|
||||
}
|
||||
|
|
@ -136,21 +129,17 @@ public class DefaultServerWebExchange implements ServerWebExchange {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Mono<MultiValueMap<String, Part>> initMultipartData(
|
||||
ServerHttpRequest request, ServerCodecConfigurer codecConfigurer) {
|
||||
private static Mono<MultiValueMap<String, Part>> initMultipartData(ServerHttpRequest request,
|
||||
ServerCodecConfigurer configurer) {
|
||||
|
||||
MediaType contentType;
|
||||
try {
|
||||
contentType = request.getHeaders().getContentType();
|
||||
if (MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
|
||||
return ((HttpMessageReader<MultiValueMap<String, Part>>) codecConfigurer
|
||||
.getReaders()
|
||||
.stream()
|
||||
.filter(reader -> reader.canRead(MULTIPART_VALUE_TYPE, MULTIPART_FORM_DATA))
|
||||
MediaType contentType = request.getHeaders().getContentType();
|
||||
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
|
||||
return ((HttpMessageReader<MultiValueMap<String, Part>>) configurer.getReaders().stream()
|
||||
.filter(reader -> reader.canRead(MULTIPART_DATA_TYPE, MediaType.MULTIPART_FORM_DATA))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"Could not find HttpMessageReader that supports " + MULTIPART_FORM_DATA)))
|
||||
.readMono(FORM_DATA_VALUE_TYPE, request, Collections.emptyMap())
|
||||
.orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader.")))
|
||||
.readMono(MULTIPART_DATA_TYPE, request, Collections.emptyMap())
|
||||
.switchIfEmpty(EMPTY_MULTIPART_DATA)
|
||||
.cache();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
|
@ -61,13 +62,16 @@ public class RequestPartMethodArgumentResolver extends AbstractNamedValueArgumen
|
|||
|
||||
@Override
|
||||
protected Mono<Object> resolveName(String name, MethodParameter param, ServerWebExchange exchange) {
|
||||
return exchange.getMultipartData().flatMap(allParts -> {
|
||||
List<Part> parts = allParts.get(name);
|
||||
if (CollectionUtils.isEmpty(parts)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.just(parts.size() == 1 ? parts.get(0) : parts);
|
||||
});
|
||||
|
||||
Mono<Object> partsMono = exchange.getMultipartData()
|
||||
.filter(map -> !CollectionUtils.isEmpty(map.get(name)))
|
||||
.map(map -> {
|
||||
List<Part> parts = map.get(name);
|
||||
return parts.size() == 1 ? parts.get(0) : parts;
|
||||
});
|
||||
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(param.getParameterType());
|
||||
return (adapter != null ? Mono.just(adapter.fromPublisher(partsMono)) : partsMono);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.http.codec.multipart.FormFieldPart;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
|
|
@ -152,8 +154,9 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
|
|||
static class MultipartController {
|
||||
|
||||
@PostMapping("/requestPart")
|
||||
void requestPart(@RequestPart Part fooPart) {
|
||||
assertEquals("foo.txt", ((FilePart) fooPart).getFilename());
|
||||
void requestPart(@RequestPart FormFieldPart barPart, @RequestPart Mono<FilePart> fooPart) {
|
||||
assertEquals("bar", barPart.getValue());
|
||||
assertEquals("foo.txt", fooPart.block(Duration.ZERO).getFilename());
|
||||
}
|
||||
|
||||
@PostMapping("/requestBodyMap")
|
||||
|
|
|
|||
Loading…
Reference in New Issue