From 6f3051c677f30fd1459a998d5fb3eba8129ecb58 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 3 May 2017 23:03:47 -0400 Subject: [PATCH] Support for @RequestPart with reactive type wrapper Issue: SPR-14546 --- .../adapter/DefaultServerWebExchange.java | 47 +++++++------------ .../RequestPartMethodArgumentResolver.java | 18 ++++--- .../annotation/MultipartIntegrationTests.java | 7 ++- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java index 52967cc0e2..bb8e75f712 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java @@ -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 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> EMPTY_FORM_DATA = @@ -110,21 +107,17 @@ public class DefaultServerWebExchange implements ServerWebExchange { } @SuppressWarnings("unchecked") - private static Mono> initFormData( - ServerHttpRequest request, ServerCodecConfigurer codecConfigurer) { + private static Mono> initFormData(ServerHttpRequest request, + ServerCodecConfigurer configurer) { - MediaType contentType; try { - contentType = request.getHeaders().getContentType(); - if (APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) { - return ((HttpMessageReader>)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>) 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> initMultipartData( - ServerHttpRequest request, ServerCodecConfigurer codecConfigurer) { + private static Mono> initMultipartData(ServerHttpRequest request, + ServerCodecConfigurer configurer) { - MediaType contentType; try { - contentType = request.getHeaders().getContentType(); - if (MULTIPART_FORM_DATA.isCompatibleWith(contentType)) { - return ((HttpMessageReader>) 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>) 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(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java index fd78c652ce..0cd3d7f13a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java @@ -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 resolveName(String name, MethodParameter param, ServerWebExchange exchange) { - return exchange.getMultipartData().flatMap(allParts -> { - List parts = allParts.get(name); - if (CollectionUtils.isEmpty(parts)) { - return Mono.empty(); - } - return Mono.just(parts.size() == 1 ? parts.get(0) : parts); - }); + + Mono partsMono = exchange.getMultipartData() + .filter(map -> !CollectionUtils.isEmpty(map.get(name))) + .map(map -> { + List 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 diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java index c2d026c510..057ea2b033 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java @@ -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 fooPart) { + assertEquals("bar", barPart.getValue()); + assertEquals("foo.txt", fooPart.block(Duration.ZERO).getFilename()); } @PostMapping("/requestBodyMap") -- GitLab