MultipartHttpMessageWriter consumes source once only

The previous fix #09f1f7 did not actually address the issue but only
moved it further down, so instead of the subscribe(), it was consuming
it inside the MultipartHttpMessageWriter#write method which returned
this.body.then(), and then again for the actual request body writing.

In this commit MultipartHttpMessageWriter#write returns Mono.empty()
since we don't actually want to write the part content from there, but
only want to access it as soon as it is availabele, for writing to
the request body.

Issue: SPR-16402
This commit is contained in:
Rossen Stoyanchev
2018-01-19 20:55:22 -05:00
parent 572c668726
commit afd248da8a
2 changed files with 55 additions and 8 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.http.codec.multipart;
import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
@@ -25,6 +26,7 @@ import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.UnicastProcessor;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.StringDecoder;
@@ -146,9 +148,48 @@ public class MultipartHttpMessageWriterTests {
Collections.emptyMap()).block(Duration.ZERO);
assertEquals("foobarbaz", value);
}
@Test // SPR-16402
public void singleSubscriberWithResource() throws IOException {
UnicastProcessor<Resource> processor = UnicastProcessor.create();
Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
Mono.just(logo).subscribe(processor);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.asyncPart("logo", processor, Resource.class);
Mono<MultiValueMap<String, HttpEntity<?>>> result = Mono.just(bodyBuilder.build());
MockServerHttpResponse response = new MockServerHttpResponse();
Map<String, Object> hints = Collections.emptyMap();
this.writer.write(result, null, MediaType.MULTIPART_FORM_DATA, response, hints).block();
MultiValueMap<String, Part> requestParts = parse(response, hints);
assertEquals(1, requestParts.size());
Part part = requestParts.getFirst("logo");
assertEquals("logo", part.name());
// TODO: a Resource written as an async part doesn't have a file name in the contentDisposition
// assertTrue(part instanceof FilePart);
// assertEquals("logo.jpg", ((FilePart) part).filename());
assertEquals(MediaType.IMAGE_JPEG, part.headers().getContentType());
assertEquals(logo.getFile().length(), part.headers().getContentLength());
}
@Test // SPR-16402
public void singleSubscriberWithStrings() {
UnicastProcessor<String> processor = UnicastProcessor.create();
Flux.just("foo", "bar", "baz").subscribe(processor);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.asyncPart("name", processor, String.class);
Mono<MultiValueMap<String, HttpEntity<?>>> result = Mono.just(bodyBuilder.build());
MockServerHttpResponse response = new MockServerHttpResponse();
Map<String, Object> hints = Collections.emptyMap();
this.writer.write(result, null, MediaType.MULTIPART_FORM_DATA, response, hints).block();
}
private MultiValueMap<String, Part> parse(MockServerHttpResponse response, Map<String, Object> hints) {