diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java index e538c33b812..259a4448733 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java +++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java @@ -312,6 +312,9 @@ public abstract class MimeTypeUtils { } } + + + /** * Generate a random MIME boundary as bytes, often used in multipart mime types. */ 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 8dc91dc55a1..dbba8e07a58 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -16,7 +16,6 @@ package org.springframework.web.reactive.result.method.annotation; -import java.util.Collections; import java.util.List; import reactor.core.publisher.Flux; @@ -75,25 +74,31 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageReaderArgu boolean isRequired = (requestPart == null || requestPart.required()); String name = getPartName(parameter, requestPart); - Flux partFlux = getPartValues(name, exchange); - if (isRequired) { - partFlux = partFlux.switchIfEmpty(Flux.error(getMissingPartException(name, parameter))); - } + Flux values = exchange.getMultipartData() + .flatMapMany(map -> { + List parts = map.get(name); + if (CollectionUtils.isEmpty(parts)) { + return isRequired ? + Flux.error(getMissingPartException(name, parameter)) : + Flux.empty(); + } + return Flux.fromIterable(parts); + }); ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameter.getParameterType()); MethodParameter elementType = adapter != null ? parameter.nested() : parameter; if (Part.class.isAssignableFrom(elementType.getNestedParameterType())) { if (adapter != null) { - partFlux = adapter.isMultiValue() ? partFlux : partFlux.take(1); - return Mono.just(adapter.fromPublisher(partFlux)); + values = adapter.isMultiValue() ? values : values.take(1); + return Mono.just(adapter.fromPublisher(values)); } else { - return partFlux.next().cast(Object.class); + return values.next().cast(Object.class); } } - return partFlux.next().flatMap(part -> { + return values.next().flatMap(part -> { ServerHttpRequest partRequest = new PartServerHttpRequest(exchange.getRequest(), part); ServerWebExchange partExchange = exchange.mutate().request(partRequest).build(); return readBody(parameter, isRequired, bindingContext, partExchange); @@ -113,12 +118,6 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageReaderArgu return partName; } - private Flux getPartValues(String name, ServerWebExchange exchange) { - return exchange.getMultipartData() - .filter(map -> !CollectionUtils.isEmpty(map.get(name))) - .flatMapIterable(map -> map.getOrDefault(name, Collections.emptyList())); - } - private ServerWebInputException getMissingPartException(String name, MethodParameter param) { String reason = "Required request part '" + name + "' is not present"; return new ServerWebInputException(reason, param); diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index d3a3c3be2a1..89a734f2bbc 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -1789,27 +1789,26 @@ Content-Transfer-Encoding: 8bit ... File Data ... ---- -You can access the "meta-data" part with `@RequestPart` which would deserialize it from -JSON (similar to `@RequestBody`) through one of the configured <>: +You can access individual parts with `@RequestPart`: [source,java,indent=0] [subs="verbatim,quotes"] ---- @PostMapping("/") - public String handle(**@RequestPart("meta-data") MetaData metadata, + public String handle(**@RequestPart("meta-data") Part metadata, @RequestPart("file-data") FilePart file**) { // ... } ---- -To access multipart data sequentially, in streaming fashion, use `@RequestBody` with -`Flux` instead. For example: +To deserialize the raw part content, for example to JSON (similar to `@RequestBody`), +simply declare a concrete target Object, instead of `Part`: [source,java,indent=0] [subs="verbatim,quotes"] ---- @PostMapping("/") - public String handle(**@RequestBody Flux parts**) { + public String handle(**@RequestPart("meta-data") MetaData metadata**) { // ... } ---- @@ -1830,6 +1829,29 @@ public String handle(**@Valid** @RequestPart("meta-data") MetaData metadata, } ---- +To access all multipart data in as a `MultiValueMap` use `@RequestBody`: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @PostMapping("/") + public String handle(**@RequestBody Mono> parts**) { + // ... + } +---- + +To access multipart data sequentially, in streaming fashion, use `@RequestBody` with +`Flux` instead. For example: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @PostMapping("/") + public String handle(**@RequestBody Flux parts**) { + // ... + } +---- + [[webflux-ann-requestbody]] ==== @RequestBody