diff --git a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java
index d8cef56757..0b02f63f2e 100644
--- a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java
+++ b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java
@@ -22,6 +22,7 @@ import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
@@ -138,9 +139,23 @@ public interface ServerWebExchange {
* cached so that this method is safe to call more than once.
*
Note:the {@linkplain Part#content() contents} of each
* part is not cached, and can only be read once.
+ * @see #getParts()
*/
Mono> getMultipartData();
+ /**
+ * Return the parts of a multipart request if the Content-Type is
+ * {@code "multipart/form-data"} or an empty flux otherwise.
+ * Note: calling this method causes the request body to
+ * be read and parsed in full and the resulting {@code Flux} is
+ * cached so that this method is safe to call more than once.
+ *
Note:the {@linkplain Part#content() contents} of each
+ * part is not cached, and can only be read once.
+ * @since 5.2
+ * @see #getMultipartData()
+ */
+ Flux getParts();
+
/**
* Return the {@link LocaleContext} using the configured
* {@link org.springframework.web.server.i18n.LocaleContextResolver}.
diff --git a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java
index 2b753f466b..41e7a2c6ad 100644
--- a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java
+++ b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -20,6 +20,7 @@ import java.time.Instant;
import java.util.Map;
import java.util.function.Function;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
@@ -107,6 +108,11 @@ public class ServerWebExchangeDecorator implements ServerWebExchange {
return getDelegate().getMultipartData();
}
+ @Override
+ public Flux getParts() {
+ return getDelegate().getParts();
+ }
+
@Override
public boolean isNotModified() {
return getDelegate().isNotModified();
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 058206b5b5..cc2c65da11 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
@@ -65,8 +66,7 @@ public class DefaultServerWebExchange implements ServerWebExchange {
private static final ResolvableType FORM_DATA_TYPE =
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
- private static final ResolvableType MULTIPART_DATA_TYPE = ResolvableType.forClassWithGenerics(
- MultiValueMap.class, String.class, Part.class);
+ private static final ResolvableType PARTS_DATA_TYPE = ResolvableType.forClass(Part.class);
private static final Mono> EMPTY_FORM_DATA =
Mono.just(CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap(0)))
@@ -91,6 +91,8 @@ public class DefaultServerWebExchange implements ServerWebExchange {
private final Mono> multipartDataMono;
+ private final Flux partFlux;
+
@Nullable
private final ApplicationContext applicationContext;
@@ -129,7 +131,8 @@ public class DefaultServerWebExchange implements ServerWebExchange {
this.sessionMono = sessionManager.getSession(this).cache();
this.localeContextResolver = localeContextResolver;
this.formDataMono = initFormData(request, codecConfigurer, getLogPrefix());
- this.multipartDataMono = initMultipartData(request, codecConfigurer, getLogPrefix());
+ this.partFlux = initParts(request, codecConfigurer, getLogPrefix());
+ this.multipartDataMono = initMultipartData(this.partFlux);
this.applicationContext = applicationContext;
}
@@ -156,27 +159,33 @@ public class DefaultServerWebExchange implements ServerWebExchange {
}
@SuppressWarnings("unchecked")
- private static Mono> initMultipartData(ServerHttpRequest request,
- ServerCodecConfigurer configurer, String logPrefix) {
-
+ private static Flux initParts(ServerHttpRequest request, ServerCodecConfigurer configurer, String logPrefix) {
try {
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))
+ return ((HttpMessageReader)configurer.getReaders().stream()
+ .filter(reader -> reader.canRead(PARTS_DATA_TYPE, MediaType.MULTIPART_FORM_DATA))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader.")))
- .readMono(MULTIPART_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix))
- .switchIfEmpty(EMPTY_MULTIPART_DATA)
+ .read(PARTS_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix))
.cache();
}
}
catch (InvalidMediaTypeException ex) {
// Ignore
}
- return EMPTY_MULTIPART_DATA;
+ return Flux.empty();
}
+ private static Mono> initMultipartData(Flux parts) {
+ return parts.collect(
+ () -> (MultiValueMap) new LinkedMultiValueMap(),
+ (map, part) -> map.add(part.name(), part))
+ .switchIfEmpty(EMPTY_MULTIPART_DATA)
+ .cache();
+ }
+
+
@Override
public ServerHttpRequest getRequest() {
@@ -221,6 +230,11 @@ public class DefaultServerWebExchange implements ServerWebExchange {
return this.multipartDataMono;
}
+ @Override
+ public Flux getParts() {
+ return this.partFlux;
+ }
+
@Override
public LocaleContext getLocaleContext() {
return this.localeContextResolver.resolveLocaleContext(this);