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);