Skip to content

Commit 44659f4

Browse files
committed
Unwrap Part content in asyncPart builder method
Closes gh-22876
1 parent 6a05b97 commit 44659f4

File tree

2 files changed

+36
-14
lines changed

2 files changed

+36
-14
lines changed

spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java

Lines changed: 11 additions & 2 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.
@@ -22,12 +22,15 @@
2222
import java.util.function.Consumer;
2323

2424
import org.reactivestreams.Publisher;
25+
import reactor.core.publisher.Mono;
2526

2627
import org.springframework.core.ParameterizedTypeReference;
2728
import org.springframework.core.ResolvableType;
29+
import org.springframework.core.io.buffer.DataBuffer;
2830
import org.springframework.http.HttpEntity;
2931
import org.springframework.http.HttpHeaders;
3032
import org.springframework.http.MediaType;
33+
import org.springframework.http.codec.multipart.Part;
3134
import org.springframework.lang.Nullable;
3235
import org.springframework.util.Assert;
3336
import org.springframework.util.LinkedMultiValueMap;
@@ -121,15 +124,21 @@ public PartBuilder part(String name, Object part, @Nullable MediaType contentTyp
121124
/**
122125
* Add an asynchronous part with {@link Publisher}-based content.
123126
* @param name the name of the part to add
124-
* @param publisher the part contents
127+
* @param publisher a Publisher of content for the part
125128
* @param elementClass the type of elements contained in the publisher
126129
* @return builder that allows for further customization of part headers
127130
*/
131+
@SuppressWarnings("unchecked")
128132
public <T, P extends Publisher<T>> PartBuilder asyncPart(String name, P publisher, Class<T> elementClass) {
129133
Assert.hasLength(name, "'name' must not be empty");
130134
Assert.notNull(publisher, "'publisher' must not be null");
131135
Assert.notNull(elementClass, "'elementClass' must not be null");
132136

137+
if (Part.class.isAssignableFrom(elementClass)) {
138+
publisher = (P) Mono.from(publisher).flatMapMany(p -> ((Part) p).content());
139+
elementClass = (Class<T>) DataBuffer.class;
140+
}
141+
133142
HttpHeaders headers = new HttpHeaders();
134143
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(headers, publisher, elementClass);
135144
this.parts.add(name, builder);

spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.http.codec.multipart;
1818

1919
import java.io.IOException;
20+
import java.nio.charset.StandardCharsets;
2021
import java.time.Duration;
2122
import java.util.Collections;
2223
import java.util.List;
@@ -46,6 +47,7 @@
4647
import org.springframework.util.MultiValueMap;
4748

4849
import static org.junit.Assert.*;
50+
import static org.mockito.Mockito.*;
4951

5052
/**
5153
* @author Sebastien Deleuze
@@ -94,7 +96,13 @@ public String getFilename() {
9496
}
9597
};
9698

97-
Publisher<String> publisher = Flux.just("foo", "bar", "baz");
99+
Flux<DataBuffer> bufferPublisher = Flux.just(
100+
this.bufferFactory.wrap("Aa".getBytes(StandardCharsets.UTF_8)),
101+
this.bufferFactory.wrap("Bb".getBytes(StandardCharsets.UTF_8)),
102+
this.bufferFactory.wrap("Cc".getBytes(StandardCharsets.UTF_8))
103+
);
104+
Part mockPart = mock(Part.class);
105+
when(mockPart.content()).thenReturn(bufferPublisher);
98106

99107
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
100108
bodyBuilder.part("name 1", "value 1");
@@ -103,14 +111,15 @@ public String getFilename() {
103111
bodyBuilder.part("logo", logo);
104112
bodyBuilder.part("utf8", utf8);
105113
bodyBuilder.part("json", new Foo("bar"), MediaType.APPLICATION_JSON);
106-
bodyBuilder.asyncPart("publisher", publisher, String.class);
114+
bodyBuilder.asyncPart("publisher", Flux.just("foo", "bar", "baz"), String.class);
115+
bodyBuilder.asyncPart("partPublisher", Mono.just(mockPart), Part.class);
107116
Mono<MultiValueMap<String, HttpEntity<?>>> result = Mono.just(bodyBuilder.build());
108117

109118
Map<String, Object> hints = Collections.emptyMap();
110119
this.writer.write(result, null, MediaType.MULTIPART_FORM_DATA, this.response, hints).block(Duration.ofSeconds(5));
111120

112121
MultiValueMap<String, Part> requestParts = parse(hints);
113-
assertEquals(6, requestParts.size());
122+
assertEquals(7, requestParts.size());
114123

115124
Part part = requestParts.getFirst("name 1");
116125
assertTrue(part instanceof FormFieldPart);
@@ -145,21 +154,25 @@ public String getFilename() {
145154
part = requestParts.getFirst("json");
146155
assertEquals("json", part.name());
147156
assertEquals(MediaType.APPLICATION_JSON, part.headers().getContentType());
148-
149-
String value = StringDecoder.textPlainOnly(false).decodeToMono(part.content(),
150-
ResolvableType.forClass(String.class), MediaType.TEXT_PLAIN,
151-
Collections.emptyMap()).block(Duration.ZERO);
152-
157+
String value = decodeToString(part);
153158
assertEquals("{\"bar\":\"bar\"}", value);
154159

155160
part = requestParts.getFirst("publisher");
156161
assertEquals("publisher", part.name());
162+
value = decodeToString(part);
163+
assertEquals("foobarbaz", value);
157164

158-
value = StringDecoder.textPlainOnly(false).decodeToMono(part.content(),
159-
ResolvableType.forClass(String.class), MediaType.TEXT_PLAIN,
160-
Collections.emptyMap()).block(Duration.ZERO);
165+
part = requestParts.getFirst("partPublisher");
166+
assertEquals("partPublisher", part.name());
167+
value = decodeToString(part);
168+
assertEquals("AaBbCc", value);
169+
}
161170

162-
assertEquals("foobarbaz", value);
171+
@SuppressWarnings("ConstantConditions")
172+
private String decodeToString(Part part) {
173+
return StringDecoder.textPlainOnly().decodeToMono(part.content(),
174+
ResolvableType.forClass(String.class), MediaType.TEXT_PLAIN,
175+
Collections.emptyMap()).block(Duration.ZERO);
163176
}
164177

165178
@Test // SPR-16402

0 commit comments

Comments
 (0)