Skip to content

Commit 11c7907

Browse files
committed
Add Flux<Part> ServerWebExchange.getParts()
This commit introduces ServerWebExchange.getParts(), as an alternative, streaming way of accessing multipart data.
1 parent edd86e5 commit 11c7907

File tree

3 files changed

+48
-13
lines changed

3 files changed

+48
-13
lines changed

spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.function.Consumer;
2323
import java.util.function.Function;
2424

25+
import reactor.core.publisher.Flux;
2526
import reactor.core.publisher.Mono;
2627

2728
import org.springframework.context.ApplicationContext;
@@ -138,9 +139,23 @@ default <T> T getAttributeOrDefault(String name, T defaultValue) {
138139
* cached so that this method is safe to call more than once.
139140
* <p><strong>Note:</strong>the {@linkplain Part#content() contents} of each
140141
* part is not cached, and can only be read once.
142+
* @see #getParts()
141143
*/
142144
Mono<MultiValueMap<String, Part>> getMultipartData();
143145

146+
/**
147+
* Return the parts of a multipart request if the Content-Type is
148+
* {@code "multipart/form-data"} or an empty flux otherwise.
149+
* <p><strong>Note:</strong> calling this method causes the request body to
150+
* be read and parsed in full and the resulting {@code Flux} is
151+
* cached so that this method is safe to call more than once.
152+
* <p><strong>Note:</strong>the {@linkplain Part#content() contents} of each
153+
* part is not cached, and can only be read once.
154+
* @since 5.2
155+
* @see #getMultipartData()
156+
*/
157+
Flux<Part> getParts();
158+
144159
/**
145160
* Return the {@link LocaleContext} using the configured
146161
* {@link org.springframework.web.server.i18n.LocaleContextResolver}.

spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.Map;
2121
import java.util.function.Function;
2222

23+
import reactor.core.publisher.Flux;
2324
import reactor.core.publisher.Mono;
2425

2526
import org.springframework.context.ApplicationContext;
@@ -107,6 +108,11 @@ public Mono<MultiValueMap<String, Part>> getMultipartData() {
107108
return getDelegate().getMultipartData();
108109
}
109110

111+
@Override
112+
public Flux<Part> getParts() {
113+
return getDelegate().getParts();
114+
}
115+
110116
@Override
111117
public boolean isNotModified() {
112118
return getDelegate().isNotModified();

spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import java.util.concurrent.ConcurrentHashMap;
2626
import java.util.function.Function;
2727

28+
import reactor.core.publisher.Flux;
2829
import reactor.core.publisher.Mono;
2930

3031
import org.springframework.context.ApplicationContext;
@@ -65,8 +66,7 @@ public class DefaultServerWebExchange implements ServerWebExchange {
6566
private static final ResolvableType FORM_DATA_TYPE =
6667
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
6768

68-
private static final ResolvableType MULTIPART_DATA_TYPE = ResolvableType.forClassWithGenerics(
69-
MultiValueMap.class, String.class, Part.class);
69+
private static final ResolvableType PARTS_DATA_TYPE = ResolvableType.forClass(Part.class);
7070

7171
private static final Mono<MultiValueMap<String, String>> EMPTY_FORM_DATA =
7272
Mono.just(CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<String, String>(0)))
@@ -91,6 +91,8 @@ public class DefaultServerWebExchange implements ServerWebExchange {
9191

9292
private final Mono<MultiValueMap<String, Part>> multipartDataMono;
9393

94+
private final Flux<Part> partFlux;
95+
9496
@Nullable
9597
private final ApplicationContext applicationContext;
9698

@@ -129,7 +131,8 @@ public DefaultServerWebExchange(ServerHttpRequest request, ServerHttpResponse re
129131
this.sessionMono = sessionManager.getSession(this).cache();
130132
this.localeContextResolver = localeContextResolver;
131133
this.formDataMono = initFormData(request, codecConfigurer, getLogPrefix());
132-
this.multipartDataMono = initMultipartData(request, codecConfigurer, getLogPrefix());
134+
this.partFlux = initParts(request, codecConfigurer, getLogPrefix());
135+
this.multipartDataMono = initMultipartData(this.partFlux);
133136
this.applicationContext = applicationContext;
134137
}
135138

@@ -156,28 +159,34 @@ private static Mono<MultiValueMap<String, String>> initFormData(ServerHttpReques
156159
}
157160

158161
@SuppressWarnings("unchecked")
159-
private static Mono<MultiValueMap<String, Part>> initMultipartData(ServerHttpRequest request,
160-
ServerCodecConfigurer configurer, String logPrefix) {
161-
162+
private static Flux<Part> initParts(ServerHttpRequest request, ServerCodecConfigurer configurer, String logPrefix) {
162163
try {
163164
MediaType contentType = request.getHeaders().getContentType();
164165
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
165-
return ((HttpMessageReader<MultiValueMap<String, Part>>) configurer.getReaders().stream()
166-
.filter(reader -> reader.canRead(MULTIPART_DATA_TYPE, MediaType.MULTIPART_FORM_DATA))
166+
return ((HttpMessageReader<Part>)configurer.getReaders().stream()
167+
.filter(reader -> reader.canRead(PARTS_DATA_TYPE, MediaType.MULTIPART_FORM_DATA))
167168
.findFirst()
168169
.orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader.")))
169-
.readMono(MULTIPART_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix))
170-
.switchIfEmpty(EMPTY_MULTIPART_DATA)
170+
.read(PARTS_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix))
171171
.cache();
172172
}
173173
}
174174
catch (InvalidMediaTypeException ex) {
175175
// Ignore
176176
}
177-
return EMPTY_MULTIPART_DATA;
177+
return Flux.empty();
178+
}
179+
180+
private static Mono<MultiValueMap<String, Part>> initMultipartData(Flux<Part> parts) {
181+
return parts.collect(
182+
() -> (MultiValueMap<String, Part>) new LinkedMultiValueMap<String, Part>(),
183+
(map, part) -> map.add(part.name(), part))
184+
.switchIfEmpty(EMPTY_MULTIPART_DATA)
185+
.cache();
178186
}
179187

180188

189+
181190
@Override
182191
public ServerHttpRequest getRequest() {
183192
return this.request;
@@ -221,6 +230,11 @@ public Mono<MultiValueMap<String, Part>> getMultipartData() {
221230
return this.multipartDataMono;
222231
}
223232

233+
@Override
234+
public Flux<Part> getParts() {
235+
return this.partFlux;
236+
}
237+
224238
@Override
225239
public LocaleContext getLocaleContext() {
226240
return this.localeContextResolver.resolveLocaleContext(this);

0 commit comments

Comments
 (0)