Support data binding for multipart requests in WebFlux
Issue: SPR-14546
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user