Support data binding for multipart requests in WebFlux

Issue: SPR-14546
This commit is contained in:
Rossen Stoyanchev
2017-05-03 18:46:00 -04:00
parent b5089ac092
commit fc7bededd0
12 changed files with 378 additions and 155 deletions

View File

@@ -39,7 +39,10 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Sebastien Deleuze
@@ -114,37 +117,39 @@ public class MultipartHttpMessageWriterTests {
assertEquals(5, requestParts.size());
Part part = requestParts.getFirst("name 1");
assertTrue(part instanceof FormFieldPart);
assertEquals("name 1", part.getName());
assertEquals("value 1", part.getContentAsString().block());
assertFalse(part.getFilename().isPresent());
assertEquals("value 1", ((FormFieldPart) part).getValue());
List<Part> part2 = requestParts.get("name 2");
assertEquals(2, part2.size());
part = part2.get(0);
List<Part> parts2 = requestParts.get("name 2");
assertEquals(2, parts2.size());
part = parts2.get(0);
assertTrue(part instanceof FormFieldPart);
assertEquals("name 2", part.getName());
assertEquals("value 2+1", part.getContentAsString().block());
part = part2.get(1);
assertEquals("value 2+1", ((FormFieldPart) part).getValue());
part = parts2.get(1);
assertTrue(part instanceof FormFieldPart);
assertEquals("name 2", part.getName());
assertEquals("value 2+2", part.getContentAsString().block());
assertEquals("value 2+2", ((FormFieldPart) part).getValue());
part = requestParts.getFirst("logo");
assertTrue(part instanceof FilePart);
assertEquals("logo", part.getName());
assertTrue(part.getFilename().isPresent());
assertEquals("logo.jpg", part.getFilename().get());
assertEquals("logo.jpg", ((FilePart) part).getFilename());
assertEquals(MediaType.IMAGE_JPEG, part.getHeaders().getContentType());
assertEquals(logo.getFile().length(), part.getHeaders().getContentLength());
part = requestParts.getFirst("utf8");
assertTrue(part instanceof FilePart);
assertEquals("utf8", part.getName());
assertTrue(part.getFilename().isPresent());
assertEquals("Hall\u00F6le.jpg", part.getFilename().get());
assertEquals("Hall\u00F6le.jpg", ((FilePart) part).getFilename());
assertEquals(MediaType.IMAGE_JPEG, part.getHeaders().getContentType());
assertEquals(utf8.getFile().length(), part.getHeaders().getContentLength());
part = requestParts.getFirst("json");
assertEquals("json", part.getName());
assertEquals(MediaType.APPLICATION_JSON_UTF8, part.getHeaders().getContentType());
assertEquals("{\"bar\":\"bar\"}", part.getContentAsString().block());
assertEquals("{\"bar\":\"bar\"}", ((FormFieldPart) part).getValue());
}

View File

@@ -18,7 +18,6 @@ package org.springframework.http.codec.multipart;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import org.junit.Test;
import reactor.core.publisher.Flux;
@@ -88,10 +87,9 @@ public class SynchronossPartHttpMessageReaderTests {
assertTrue(parts.containsKey("fooPart"));
Part part = parts.getFirst("fooPart");
assertTrue(part instanceof FilePart);
assertEquals("fooPart", part.getName());
Optional<String> filename = part.getFilename();
assertTrue(filename.isPresent());
assertEquals("foo.txt", filename.get());
assertEquals("foo.txt", ((FilePart) part).getFilename());
DataBuffer buffer = part.getContent().reduce(DataBuffer::write).block();
assertEquals(12, buffer.readableByteCount());
byte[] byteContent = new byte[12];
@@ -100,10 +98,9 @@ public class SynchronossPartHttpMessageReaderTests {
assertTrue(parts.containsKey("barPart"));
part = parts.getFirst("barPart");
assertTrue(part instanceof FormFieldPart);
assertEquals("barPart", part.getName());
filename = part.getFilename();
assertFalse(filename.isPresent());
assertEquals("bar", part.getContentAsString().block());
assertEquals("bar", ((FormFieldPart) part).getValue());
}
@Test

View File

@@ -30,6 +30,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -99,12 +101,11 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
private void assertFooPart(Part part) {
assertEquals("fooPart", part.getName());
Optional<String> filename = part.getFilename();
assertTrue(filename.isPresent());
assertEquals("foo.txt", filename.get());
assertTrue(part instanceof FilePart);
assertEquals("foo.txt", ((FilePart) part).getFilename());
DataBuffer buffer = part
.getContent()
.reduce((s1, s2) -> s1.write(s2))
.reduce(DataBuffer::write)
.block();
assertEquals(12, buffer.readableByteCount());
byte[] byteContent = new byte[12];
@@ -114,9 +115,8 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
private void assertBarPart(Part part) {
assertEquals("barPart", part.getName());
Optional<String> filename = part.getFilename();
assertFalse(filename.isPresent());
assertEquals("bar", part.getContentAsString().block());
assertTrue(part instanceof FormFieldPart);
assertEquals("bar", ((FormFieldPart) part).getValue());
}
}

View File

@@ -17,15 +17,22 @@
package org.springframework.web.bind.support;
import java.beans.PropertyEditorSupport;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.Iterator;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.FormHttpMessageWriter;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.MultipartHttpMessageWriter;
import org.springframework.mock.http.client.reactive.test.MockClientHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.TestBean;
@@ -34,9 +41,12 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link WebExchangeDataBinder}.
@@ -177,39 +187,60 @@ public class WebExchangeDataBinderTests {
assertEquals("test", this.testBean.getSpouse().getName());
}
@Test
public void testMultipart() throws Exception {
private String generateForm(MultiValueMap<String, String> form) {
StringBuilder builder = new StringBuilder();
try {
for (Iterator<String> names = form.keySet().iterator(); names.hasNext();) {
String name = names.next();
for (Iterator<String> values = form.get(name).iterator(); values.hasNext();) {
String value = values.next();
builder.append(URLEncoder.encode(name, "UTF-8"));
if (value != null) {
builder.append('=');
builder.append(URLEncoder.encode(value, "UTF-8"));
if (values.hasNext()) {
builder.append('&');
}
}
}
if (names.hasNext()) {
builder.append('&');
}
}
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
return builder.toString();
MultipartBean bean = new MultipartBean();
WebExchangeDataBinder binder = new WebExchangeDataBinder(bean);
MultiValueMap<String, Object> data = new LinkedMultiValueMap<>();
data.add("name", "bar");
data.add("someList", "123");
data.add("someList", "abc");
data.add("someArray", "dec");
data.add("someArray", "456");
data.add("part", new ClassPathResource("org/springframework/http/codec/multipart/foo.txt"));
data.add("somePartList", new ClassPathResource("org/springframework/http/codec/multipart/foo.txt"));
data.add("somePartList", new ClassPathResource("org/springframework/http/server/reactive/spring.png"));
binder.bind(exchangeMultipart(data)).block(Duration.ofMillis(5000));
assertEquals("bar", bean.getName());
assertEquals(Arrays.asList("123", "abc"), bean.getSomeList());
assertArrayEquals(new String[] {"dec", "456"}, bean.getSomeArray());
assertEquals("foo.txt", bean.getPart().getFilename());
assertEquals(2, bean.getSomePartList().size());
assertEquals("foo.txt", bean.getSomePartList().get(0).getFilename());
assertEquals("spring.png", bean.getSomePartList().get(1).getFilename());
}
private ServerWebExchange exchange(MultiValueMap<String, String> formData) {
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.POST, "/");
new FormHttpMessageWriter().write(Mono.just(formData),
forClassWithGenerics(MultiValueMap.class, String.class, String.class),
MediaType.APPLICATION_FORM_URLENCODED, request, Collections.emptyMap()).block();
return MockServerHttpRequest
.post("/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(generateForm(formData))
.body(request.getBody())
.toExchange();
}
private ServerWebExchange exchangeMultipart(MultiValueMap<String, ?> multipartData) {
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.POST, "/");
new MultipartHttpMessageWriter().write(Mono.just(multipartData), forClass(MultiValueMap.class),
MediaType.MULTIPART_FORM_DATA, request, Collections.emptyMap()).block();
return MockServerHttpRequest
.post("/")
.contentType(request.getHeaders().getContentType())
.body(request.getBody())
.toExchange();
}
@@ -222,4 +253,58 @@ public class WebExchangeDataBinderTests {
}
}
private static class MultipartBean {
private String name;
private List<?> someList;
private String[] someArray;
private FilePart part;
private List<FilePart> somePartList;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public List<?> getSomeList() {
return this.someList;
}
public void setSomeList(List<?> someList) {
this.someList = someList;
}
public String[] getSomeArray() {
return this.someArray;
}
public void setSomeArray(String[] someArray) {
this.someArray = someArray;
}
public FilePart getPart() {
return this.part;
}
public void setPart(FilePart part) {
this.part = part;
}
public List<FilePart> getSomePartList() {
return this.somePartList;
}
public void setSomePartList(List<FilePart> somePartList) {
this.somePartList = somePartList;
}
}
}