Add PartHttpMessageWriter
Closes gh-24535
This commit is contained in:
@@ -49,6 +49,8 @@ import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MultipartHttpMessageWriter}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
@@ -118,39 +120,34 @@ public class MultipartHttpMessageWriterTests extends AbstractLeakCheckingTests {
|
||||
this.writer.write(result, null, MediaType.MULTIPART_FORM_DATA, this.response, hints)
|
||||
.block(Duration.ofSeconds(5));
|
||||
|
||||
MultiValueMap<String, Part> requestParts = parse(hints);
|
||||
MultiValueMap<String, Part> requestParts = parse(this.response, hints);
|
||||
assertThat(requestParts.size()).isEqualTo(7);
|
||||
|
||||
Part part = requestParts.getFirst("name 1");
|
||||
boolean condition4 = part instanceof FormFieldPart;
|
||||
assertThat(condition4).isTrue();
|
||||
assertThat(part instanceof FormFieldPart).isTrue();
|
||||
assertThat(part.name()).isEqualTo("name 1");
|
||||
assertThat(((FormFieldPart) part).value()).isEqualTo("value 1");
|
||||
|
||||
List<Part> parts2 = requestParts.get("name 2");
|
||||
assertThat(parts2.size()).isEqualTo(2);
|
||||
part = parts2.get(0);
|
||||
boolean condition3 = part instanceof FormFieldPart;
|
||||
assertThat(condition3).isTrue();
|
||||
assertThat(part instanceof FormFieldPart).isTrue();
|
||||
assertThat(part.name()).isEqualTo("name 2");
|
||||
assertThat(((FormFieldPart) part).value()).isEqualTo("value 2+1");
|
||||
part = parts2.get(1);
|
||||
boolean condition2 = part instanceof FormFieldPart;
|
||||
assertThat(condition2).isTrue();
|
||||
assertThat(part instanceof FormFieldPart).isTrue();
|
||||
assertThat(part.name()).isEqualTo("name 2");
|
||||
assertThat(((FormFieldPart) part).value()).isEqualTo("value 2+2");
|
||||
|
||||
part = requestParts.getFirst("logo");
|
||||
boolean condition1 = part instanceof FilePart;
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(part instanceof FilePart).isTrue();
|
||||
assertThat(part.name()).isEqualTo("logo");
|
||||
assertThat(((FilePart) part).filename()).isEqualTo("logo.jpg");
|
||||
assertThat(part.headers().getContentType()).isEqualTo(MediaType.IMAGE_JPEG);
|
||||
assertThat(part.headers().getContentLength()).isEqualTo(logo.getFile().length());
|
||||
|
||||
part = requestParts.getFirst("utf8");
|
||||
boolean condition = part instanceof FilePart;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(part instanceof FilePart).isTrue();
|
||||
assertThat(part.name()).isEqualTo("utf8");
|
||||
assertThat(((FilePart) part).filename()).isEqualTo("Hall\u00F6le.jpg");
|
||||
assertThat(part.headers().getContentType()).isEqualTo(MediaType.IMAGE_JPEG);
|
||||
@@ -195,7 +192,7 @@ public class MultipartHttpMessageWriterTests extends AbstractLeakCheckingTests {
|
||||
assertThat(contentType.getParameter("boundary")).isNotEmpty();
|
||||
assertThat(contentType.getParameter("charset")).isEqualTo("UTF-8");
|
||||
|
||||
MultiValueMap<String, Part> requestParts = parse(hints);
|
||||
MultiValueMap<String, Part> requestParts = parse(this.response, hints);
|
||||
assertThat(requestParts.size()).isEqualTo(2);
|
||||
assertThat(requestParts.getFirst("name 1").name()).isEqualTo("name 1");
|
||||
assertThat(requestParts.getFirst("name 2").name()).isEqualTo("name 2");
|
||||
@@ -222,13 +219,12 @@ public class MultipartHttpMessageWriterTests extends AbstractLeakCheckingTests {
|
||||
Map<String, Object> hints = Collections.emptyMap();
|
||||
this.writer.write(result, null, MediaType.MULTIPART_FORM_DATA, this.response, hints).block();
|
||||
|
||||
MultiValueMap<String, Part> requestParts = parse(hints);
|
||||
MultiValueMap<String, Part> requestParts = parse(this.response, hints);
|
||||
assertThat(requestParts.size()).isEqualTo(1);
|
||||
|
||||
Part part = requestParts.getFirst("logo");
|
||||
assertThat(part.name()).isEqualTo("logo");
|
||||
boolean condition = part instanceof FilePart;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(part instanceof FilePart).isTrue();
|
||||
assertThat(((FilePart) part).filename()).isEqualTo("logo.jpg");
|
||||
assertThat(part.headers().getContentType()).isEqualTo(MediaType.IMAGE_JPEG);
|
||||
assertThat(part.headers().getContentLength()).isEqualTo(logo.getFile().length());
|
||||
@@ -273,24 +269,22 @@ public class MultipartHttpMessageWriterTests extends AbstractLeakCheckingTests {
|
||||
this.writer.write(Mono.just(multipartData), null, MediaType.MULTIPART_FORM_DATA,
|
||||
this.response, hints).block();
|
||||
|
||||
MultiValueMap<String, Part> requestParts = parse(hints);
|
||||
MultiValueMap<String, Part> requestParts = parse(this.response, hints);
|
||||
assertThat(requestParts.size()).isEqualTo(2);
|
||||
|
||||
Part part = requestParts.getFirst("resource");
|
||||
boolean condition1 = part instanceof FilePart;
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(part instanceof FilePart).isTrue();
|
||||
assertThat(((FilePart) part).filename()).isEqualTo("spring.jpg");
|
||||
assertThat(part.headers().getContentLength()).isEqualTo(logo.getFile().length());
|
||||
|
||||
part = requestParts.getFirst("buffers");
|
||||
boolean condition = part instanceof FilePart;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(part instanceof FilePart).isTrue();
|
||||
assertThat(((FilePart) part).filename()).isEqualTo("buffers.jpg");
|
||||
assertThat(part.headers().getContentLength()).isEqualTo(logo.getFile().length());
|
||||
}
|
||||
|
||||
private MultiValueMap<String, Part> parse(Map<String, Object> hints) {
|
||||
MediaType contentType = this.response.getHeaders().getContentType();
|
||||
static MultiValueMap<String, Part> parse(MockServerHttpResponse response, Map<String, Object> hints) {
|
||||
MediaType contentType = response.getHeaders().getContentType();
|
||||
assertThat(contentType.getParameter("boundary")).as("No boundary found").isNotNull();
|
||||
|
||||
// see if Synchronoss NIO Multipart can read what we wrote
|
||||
@@ -299,7 +293,7 @@ public class MultipartHttpMessageWriterTests extends AbstractLeakCheckingTests {
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/")
|
||||
.contentType(MediaType.parseMediaType(contentType.toString()))
|
||||
.body(this.response.getBody());
|
||||
.body(response.getBody());
|
||||
|
||||
ResolvableType elementType = ResolvableType.forClassWithGenerics(
|
||||
MultiValueMap.class, String.class, Part.class);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.http.codec.multipart;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.http.codec.multipart.MultipartHttpMessageWriterTests.parse;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PartHttpMessageWriter}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
public class PartHttpMessageWriterTests extends AbstractLeakCheckingTests {
|
||||
|
||||
private final PartHttpMessageWriter writer = new PartHttpMessageWriter();
|
||||
|
||||
private final MockServerHttpResponse response = new MockServerHttpResponse(this.bufferFactory);
|
||||
|
||||
|
||||
@Test
|
||||
public void canWrite() {
|
||||
assertThat(this.writer.canWrite(
|
||||
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Object.class),
|
||||
MediaType.MULTIPART_FORM_DATA)).isTrue();
|
||||
assertThat(this.writer.canWrite(
|
||||
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class),
|
||||
MediaType.MULTIPART_FORM_DATA)).isTrue();
|
||||
assertThat(this.writer.canWrite(
|
||||
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Object.class),
|
||||
MediaType.MULTIPART_MIXED)).isTrue();
|
||||
assertThat(this.writer.canWrite(
|
||||
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Object.class),
|
||||
MediaType.MULTIPART_RELATED)).isTrue();
|
||||
|
||||
assertThat(this.writer.canWrite(
|
||||
ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class),
|
||||
MediaType.MULTIPART_FORM_DATA)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.TEXT_PLAIN);
|
||||
Part textPart = mock(Part.class);
|
||||
given(textPart.name()).willReturn("text part");
|
||||
given(textPart.headers()).willReturn(headers);
|
||||
given(textPart.content()).willReturn(Flux.just(
|
||||
this.bufferFactory.wrap("text1".getBytes(StandardCharsets.UTF_8)),
|
||||
this.bufferFactory.wrap("text2".getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
FilePart filePart = mock(FilePart.class);
|
||||
given(filePart.name()).willReturn("file part");
|
||||
given(filePart.headers()).willReturn(new HttpHeaders());
|
||||
given(filePart.filename()).willReturn("file.txt");
|
||||
given(filePart.content()).willReturn(Flux.just(
|
||||
this.bufferFactory.wrap("Aa".getBytes(StandardCharsets.UTF_8)),
|
||||
this.bufferFactory.wrap("Bb".getBytes(StandardCharsets.UTF_8)),
|
||||
this.bufferFactory.wrap("Cc".getBytes(StandardCharsets.UTF_8))
|
||||
));
|
||||
|
||||
Map<String, Object> hints = Collections.emptyMap();
|
||||
this.writer.write(Flux.just(textPart, filePart), null, MediaType.MULTIPART_FORM_DATA, this.response, hints)
|
||||
.block(Duration.ofSeconds(5));
|
||||
|
||||
MultiValueMap<String, Part> requestParts = parse(this.response, hints);
|
||||
assertThat(requestParts.size()).isEqualTo(2);
|
||||
|
||||
Part part = requestParts.getFirst("text part");
|
||||
assertThat(part.name()).isEqualTo("text part");
|
||||
assertThat(part.headers().getContentType()).isEqualTo(MediaType.TEXT_PLAIN);
|
||||
String value = decodeToString(part);
|
||||
assertThat(value).isEqualTo("text1text2");
|
||||
|
||||
part = requestParts.getFirst("file part");
|
||||
assertThat(part.name()).isEqualTo("file part");
|
||||
assertThat(((FilePart) part).filename()).isEqualTo("file.txt");
|
||||
assertThat(decodeToString(part)).isEqualTo("AaBbCc");
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private String decodeToString(Part part) {
|
||||
return StringDecoder.textPlainOnly().decodeToMono(part.content(),
|
||||
ResolvableType.forClass(String.class), MediaType.TEXT_PLAIN,
|
||||
Collections.emptyMap()).block(Duration.ZERO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.codec.json.Jackson2SmileDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2SmileEncoder;
|
||||
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||
import org.springframework.http.codec.multipart.PartHttpMessageWriter;
|
||||
import org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader;
|
||||
import org.springframework.http.codec.protobuf.ProtobufDecoder;
|
||||
import org.springframework.http.codec.protobuf.ProtobufHttpMessageWriter;
|
||||
@@ -102,7 +103,7 @@ public class ServerCodecConfigurerTests {
|
||||
@Test
|
||||
public void defaultWriters() {
|
||||
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
|
||||
assertThat(writers.size()).isEqualTo(12);
|
||||
assertThat(writers.size()).isEqualTo(13);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
|
||||
@@ -110,6 +111,7 @@ public class ServerCodecConfigurerTests {
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class);
|
||||
assertStringEncoder(getNextEncoder(writers), true);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
|
||||
assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartHttpMessageWriter.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2JsonEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2SmileEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jaxb2XmlEncoder.class);
|
||||
|
||||
Reference in New Issue
Block a user