Add multipart support to WebFlux functional API
Issue: SPR-14546
This commit is contained in:
parent
852dc84d38
commit
a712c19661
|
@ -89,6 +89,7 @@ configure(allprojects) { project ->
|
||||||
ext.servletVersion = "3.1.0"
|
ext.servletVersion = "3.1.0"
|
||||||
ext.slf4jVersion = "1.7.25"
|
ext.slf4jVersion = "1.7.25"
|
||||||
ext.snakeyamlVersion = "1.18"
|
ext.snakeyamlVersion = "1.18"
|
||||||
|
ext.nioMultipartVersion = "1.0.2"
|
||||||
ext.testngVersion = "6.11"
|
ext.testngVersion = "6.11"
|
||||||
ext.tiles3Version = "3.0.7"
|
ext.tiles3Version = "3.0.7"
|
||||||
ext.tomcatVersion = "8.5.14"
|
ext.tomcatVersion = "8.5.14"
|
||||||
|
@ -747,7 +748,7 @@ project("spring-web") {
|
||||||
optional("javax.xml.bind:jaxb-api:${jaxbVersion}")
|
optional("javax.xml.bind:jaxb-api:${jaxbVersion}")
|
||||||
optional("javax.xml.ws:jaxws-api:${jaxwsVersion}")
|
optional("javax.xml.ws:jaxws-api:${jaxwsVersion}")
|
||||||
optional("javax.mail:javax.mail-api:${javamailVersion}")
|
optional("javax.mail:javax.mail-api:${javamailVersion}")
|
||||||
optional("org.synchronoss.cloud:nio-multipart-parser:1.0.2")
|
optional("org.synchronoss.cloud:nio-multipart-parser:${nioMultipartVersion}")
|
||||||
optional("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}")
|
optional("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}")
|
||||||
testCompile(project(":spring-context-support")) // for JafMediaTypeFactory
|
testCompile(project(":spring-context-support")) // for JafMediaTypeFactory
|
||||||
testCompile("io.projectreactor.addons:reactor-test")
|
testCompile("io.projectreactor.addons:reactor-test")
|
||||||
|
@ -839,6 +840,10 @@ project("spring-webflux") {
|
||||||
testRuntime("org.jetbrains.kotlin:kotlin-compiler:${kotlinVersion}")
|
testRuntime("org.jetbrains.kotlin:kotlin-compiler:${kotlinVersion}")
|
||||||
testCompile("org.jetbrains.kotlin:kotlin-script-runtime:${kotlinVersion}")
|
testCompile("org.jetbrains.kotlin:kotlin-script-runtime:${kotlinVersion}")
|
||||||
testRuntime("org.jetbrains.kotlin:kotlin-script-util:${kotlinVersion}")
|
testRuntime("org.jetbrains.kotlin:kotlin-script-util:${kotlinVersion}")
|
||||||
|
testRuntime("org.synchronoss.cloud:nio-multipart-parser:${nioMultipartVersion}")
|
||||||
|
testRuntime("com.sun.mail:javax.mail:${javamailVersion}")
|
||||||
|
testRuntime("com.sun.xml.bind:jaxb-core:${jaxbVersion}")
|
||||||
|
testRuntime("com.sun.xml.bind:jaxb-impl:${jaxbVersion}")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JavaVersion.current().java9Compatible) {
|
if (JavaVersion.current().java9Compatible) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
||||||
import org.springframework.core.codec.Decoder;
|
import org.springframework.core.codec.Decoder;
|
||||||
import org.springframework.core.codec.StringDecoder;
|
import org.springframework.core.codec.StringDecoder;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||||
|
import org.springframework.http.codec.multipart.MultipartHttpMessageWriter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of {@link ClientCodecConfigurer}.
|
* Default implementation of {@link ClientCodecConfigurer}.
|
||||||
|
@ -57,6 +58,7 @@ class DefaultClientCodecConfigurer extends DefaultCodecConfigurer implements Cli
|
||||||
protected void addTypedWritersTo(List<HttpMessageWriter<?>> result) {
|
protected void addTypedWritersTo(List<HttpMessageWriter<?>> result) {
|
||||||
super.addTypedWritersTo(result);
|
super.addTypedWritersTo(result);
|
||||||
addWriterTo(result, FormHttpMessageWriter::new);
|
addWriterTo(result, FormHttpMessageWriter::new);
|
||||||
|
addWriterTo(result, MultipartHttpMessageWriter::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -21,6 +21,8 @@ import java.util.List;
|
||||||
import org.springframework.core.codec.Encoder;
|
import org.springframework.core.codec.Encoder;
|
||||||
import org.springframework.core.codec.StringDecoder;
|
import org.springframework.core.codec.StringDecoder;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||||
|
import org.springframework.http.codec.multipart.SynchronossMultipartHttpMessageReader;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of {@link ServerCodecConfigurer}.
|
* Default implementation of {@link ServerCodecConfigurer}.
|
||||||
|
@ -30,6 +32,11 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||||
*/
|
*/
|
||||||
class DefaultServerCodecConfigurer extends DefaultCodecConfigurer implements ServerCodecConfigurer {
|
class DefaultServerCodecConfigurer extends DefaultCodecConfigurer implements ServerCodecConfigurer {
|
||||||
|
|
||||||
|
static final boolean synchronossMultipartPresent =
|
||||||
|
ClassUtils.isPresent("org.synchronoss.cloud.nio.multipart.NioMultipartParser",
|
||||||
|
org.springframework.http.codec.DefaultCodecConfigurer.class.getClassLoader());
|
||||||
|
|
||||||
|
|
||||||
public DefaultServerCodecConfigurer() {
|
public DefaultServerCodecConfigurer() {
|
||||||
super(new DefaultServerDefaultCodecsConfigurer());
|
super(new DefaultServerDefaultCodecsConfigurer());
|
||||||
}
|
}
|
||||||
|
@ -57,6 +64,9 @@ class DefaultServerCodecConfigurer extends DefaultCodecConfigurer implements Ser
|
||||||
public void addTypedReadersTo(List<HttpMessageReader<?>> result) {
|
public void addTypedReadersTo(List<HttpMessageReader<?>> result) {
|
||||||
super.addTypedReadersTo(result);
|
super.addTypedReadersTo(result);
|
||||||
addReaderTo(result, FormHttpMessageReader::new);
|
addReaderTo(result, FormHttpMessageReader::new);
|
||||||
|
if (synchronossMultipartPresent) {
|
||||||
|
addReaderTo(result, SynchronossMultipartHttpMessageReader::new);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||||
|
import org.springframework.http.codec.multipart.MultipartHttpMessageWriter;
|
||||||
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
||||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
|
@ -76,13 +77,14 @@ public class ClientCodecConfigurerTests {
|
||||||
@Test
|
@Test
|
||||||
public void defaultWriters() throws Exception {
|
public void defaultWriters() throws Exception {
|
||||||
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
|
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
|
||||||
assertEquals(9, writers.size());
|
assertEquals(10, writers.size());
|
||||||
assertEquals(ByteArrayEncoder.class, getNextEncoder(writers).getClass());
|
assertEquals(ByteArrayEncoder.class, getNextEncoder(writers).getClass());
|
||||||
assertEquals(ByteBufferEncoder.class, getNextEncoder(writers).getClass());
|
assertEquals(ByteBufferEncoder.class, getNextEncoder(writers).getClass());
|
||||||
assertEquals(DataBufferEncoder.class, getNextEncoder(writers).getClass());
|
assertEquals(DataBufferEncoder.class, getNextEncoder(writers).getClass());
|
||||||
assertEquals(ResourceHttpMessageWriter.class, writers.get(index.getAndIncrement()).getClass());
|
assertEquals(ResourceHttpMessageWriter.class, writers.get(index.getAndIncrement()).getClass());
|
||||||
assertStringEncoder(getNextEncoder(writers), true);
|
assertStringEncoder(getNextEncoder(writers), true);
|
||||||
assertEquals(FormHttpMessageWriter.class, writers.get(this.index.getAndIncrement()).getClass());
|
assertEquals(FormHttpMessageWriter.class, writers.get(this.index.getAndIncrement()).getClass());
|
||||||
|
assertEquals(MultipartHttpMessageWriter.class, writers.get(this.index.getAndIncrement()).getClass());
|
||||||
assertEquals(Jaxb2XmlEncoder.class, getNextEncoder(writers).getClass());
|
assertEquals(Jaxb2XmlEncoder.class, getNextEncoder(writers).getClass());
|
||||||
assertEquals(Jackson2JsonEncoder.class, getNextEncoder(writers).getClass());
|
assertEquals(Jackson2JsonEncoder.class, getNextEncoder(writers).getClass());
|
||||||
assertStringEncoder(getNextEncoder(writers), false);
|
assertStringEncoder(getNextEncoder(writers), false);
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||||
|
import org.springframework.http.codec.multipart.SynchronossMultipartHttpMessageReader;
|
||||||
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
||||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
|
@ -62,13 +63,14 @@ public class ServerCodecConfigurerTests {
|
||||||
@Test
|
@Test
|
||||||
public void defaultReaders() throws Exception {
|
public void defaultReaders() throws Exception {
|
||||||
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
|
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
|
||||||
assertEquals(9, readers.size());
|
assertEquals(10, readers.size());
|
||||||
assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass());
|
assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass());
|
||||||
assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass());
|
assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass());
|
||||||
assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass());
|
assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass());
|
||||||
assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass());
|
assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass());
|
||||||
assertStringDecoder(getNextDecoder(readers), true);
|
assertStringDecoder(getNextDecoder(readers), true);
|
||||||
assertEquals(FormHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass());
|
assertEquals(FormHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass());
|
||||||
|
assertEquals(SynchronossMultipartHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass());
|
||||||
assertEquals(Jaxb2XmlDecoder.class, getNextDecoder(readers).getClass());
|
assertEquals(Jaxb2XmlDecoder.class, getNextDecoder(readers).getClass());
|
||||||
assertEquals(Jackson2JsonDecoder.class, getNextDecoder(readers).getClass());
|
assertEquals(Jackson2JsonDecoder.class, getNextDecoder(readers).getClass());
|
||||||
assertStringDecoder(getNextDecoder(readers), false);
|
assertStringDecoder(getNextDecoder(readers), false);
|
||||||
|
|
|
@ -33,15 +33,19 @@ import org.springframework.http.HttpMessage;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ReactiveHttpInputMessage;
|
import org.springframework.http.ReactiveHttpInputMessage;
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import static org.springframework.http.codec.multipart.MultipartHttpMessageReader.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementations of {@link BodyExtractor} that read various bodies, such a reactive streams.
|
* Implementations of {@link BodyExtractor} that read various bodies, such a reactive streams.
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
|
* @author Sebastien Deleuze
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public abstract class BodyExtractors {
|
public abstract class BodyExtractors {
|
||||||
|
@ -135,6 +139,23 @@ public abstract class BodyExtractors {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@code BodyExtractor} that reads form data into a {@link MultiValueMap}.
|
||||||
|
* @return a {@code BodyExtractor} that reads multipart data
|
||||||
|
*/
|
||||||
|
// Note that the returned BodyExtractor is parameterized to ServerHttpRequest, not
|
||||||
|
// ReactiveHttpInputMessage like other methods, since reading form data only typically happens on
|
||||||
|
// the server-side
|
||||||
|
public static BodyExtractor<Mono<MultiValueMap<String, Part>>, ServerHttpRequest> toMultipartData() {
|
||||||
|
return (serverRequest, context) -> {
|
||||||
|
HttpMessageReader<MultiValueMap<String, Part>> messageReader =
|
||||||
|
multipartMessageReader(context);
|
||||||
|
return context.serverResponse()
|
||||||
|
.map(serverResponse -> messageReader.readMono(MULTIPART_VALUE_TYPE, MULTIPART_VALUE_TYPE, serverRequest, serverResponse, context.hints()))
|
||||||
|
.orElseGet(() -> messageReader.readMono(MULTIPART_VALUE_TYPE, serverRequest, context.hints()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@code BodyExtractor} that returns the body of the message as a {@link Flux} of
|
* Return a {@code BodyExtractor} that returns the body of the message as a {@link Flux} of
|
||||||
* {@link DataBuffer}s.
|
* {@link DataBuffer}s.
|
||||||
|
@ -180,6 +201,17 @@ public abstract class BodyExtractors {
|
||||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE));
|
MediaType.APPLICATION_FORM_URLENCODED_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static HttpMessageReader<MultiValueMap<String, Part>> multipartMessageReader(BodyExtractor.Context context) {
|
||||||
|
return context.messageReaders().get()
|
||||||
|
.filter(messageReader -> messageReader
|
||||||
|
.canRead(MULTIPART_VALUE_TYPE, MediaType.MULTIPART_FORM_DATA))
|
||||||
|
.findFirst()
|
||||||
|
.map(BodyExtractors::<MultiValueMap<String, Part>>cast)
|
||||||
|
.orElseThrow(() -> new IllegalStateException(
|
||||||
|
"Could not find HttpMessageReader that supports " +
|
||||||
|
MediaType.MULTIPART_FORM_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
private static MediaType contentType(HttpMessage message) {
|
private static MediaType contentType(HttpMessage message) {
|
||||||
MediaType result = message.getHeaders().getContentType();
|
MediaType result = message.getHeaders().getContentType();
|
||||||
return result != null ? result : MediaType.APPLICATION_OCTET_STREAM;
|
return result != null ? result : MediaType.APPLICATION_OCTET_STREAM;
|
||||||
|
|
|
@ -33,11 +33,15 @@ import org.springframework.http.ReactiveHttpOutputMessage;
|
||||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||||
import org.springframework.http.codec.HttpMessageWriter;
|
import org.springframework.http.codec.HttpMessageWriter;
|
||||||
import org.springframework.http.codec.ServerSentEvent;
|
import org.springframework.http.codec.ServerSentEvent;
|
||||||
|
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||||
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import static org.springframework.http.codec.multipart.MultipartHttpMessageReader.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementations of {@link BodyInserter} that write various bodies, such a reactive streams,
|
* Implementations of {@link BodyInserter} that write various bodies, such a reactive streams,
|
||||||
* server-sent events, resources, etc.
|
* server-sent events, resources, etc.
|
||||||
|
@ -243,6 +247,27 @@ public abstract class BodyInserters {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@code BodyInserter} that writes the given {@code MultiValueMap} as Multipart
|
||||||
|
* data.
|
||||||
|
* @param multipartData the form data to write to the output message
|
||||||
|
* @return a {@code BodyInserter} that writes form data
|
||||||
|
*/
|
||||||
|
// Note that the returned BodyInserter is parameterized to ClientHttpRequest, not
|
||||||
|
// ReactiveHttpOutputMessage like other methods, since sending form data only typically happens
|
||||||
|
// on the server-side
|
||||||
|
public static BodyInserter<MultiValueMap<String, ?>, ClientHttpRequest> fromMultipartData(
|
||||||
|
MultiValueMap<String, ?> multipartData) {
|
||||||
|
|
||||||
|
Assert.notNull(multipartData, "'multipartData' must not be null");
|
||||||
|
return (outputMessage, context) -> {
|
||||||
|
HttpMessageWriter<MultiValueMap<String, ?>> messageWriter =
|
||||||
|
findMessageWriter(context, MULTIPART_VALUE_TYPE, MediaType.MULTIPART_FORM_DATA);
|
||||||
|
return messageWriter.write(Mono.just(multipartData), FORM_TYPE,
|
||||||
|
MediaType.MULTIPART_FORM_DATA, outputMessage, context.hints());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@code BodyInserter} that writes the given {@code Publisher<DataBuffer>} to the body.
|
* Return a {@code BodyInserter} that writes the given {@code Publisher<DataBuffer>} to the body.
|
||||||
* @param publisher the data buffer publisher to write
|
* @param publisher the data buffer publisher to write
|
||||||
|
|
|
@ -103,7 +103,7 @@ public class DelegatingWebFluxConfigurationTests {
|
||||||
verify(webFluxConfigurer).configureArgumentResolvers(any());
|
verify(webFluxConfigurer).configureArgumentResolvers(any());
|
||||||
|
|
||||||
assertSame(formatterRegistry.getValue(), initializerConversionService);
|
assertSame(formatterRegistry.getValue(), initializerConversionService);
|
||||||
assertEquals(9, codecsConfigurer.getValue().getReaders().size());
|
assertEquals(10, codecsConfigurer.getValue().getReaders().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class WebFluxConfigurationSupportTests {
|
||||||
assertNotNull(adapter);
|
assertNotNull(adapter);
|
||||||
|
|
||||||
List<HttpMessageReader<?>> readers = adapter.getMessageCodecConfigurer().getReaders();
|
List<HttpMessageReader<?>> readers = adapter.getMessageCodecConfigurer().getReaders();
|
||||||
assertEquals(9, readers.size());
|
assertEquals(10, readers.size());
|
||||||
|
|
||||||
assertHasMessageReader(readers, forClass(byte[].class), APPLICATION_OCTET_STREAM);
|
assertHasMessageReader(readers, forClass(byte[].class), APPLICATION_OCTET_STREAM);
|
||||||
assertHasMessageReader(readers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM);
|
assertHasMessageReader(readers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM);
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2017 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
|
||||||
|
*
|
||||||
|
* http://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.web.reactive.function;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.codec.multipart.Part;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.springframework.web.reactive.function.server.AbstractRouterFunctionIntegrationTests;
|
||||||
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
|
||||||
|
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||||
|
|
||||||
|
public class MultipartIntegrationTests extends AbstractRouterFunctionIntegrationTests {
|
||||||
|
|
||||||
|
private final WebClient webClient = WebClient.create();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void multipart() {
|
||||||
|
Mono<ClientResponse> result = webClient
|
||||||
|
.post()
|
||||||
|
.uri("http://localhost:" + this.port + "/")
|
||||||
|
.contentType(MediaType.MULTIPART_FORM_DATA)
|
||||||
|
.body(BodyInserters.fromMultipartData(generateBody()))
|
||||||
|
.exchange();
|
||||||
|
|
||||||
|
StepVerifier
|
||||||
|
.create(result)
|
||||||
|
.consumeNextWith(response -> {
|
||||||
|
assertEquals(HttpStatus.OK, response.statusCode());
|
||||||
|
})
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiValueMap<String, Object> generateBody() {
|
||||||
|
HttpHeaders fooHeaders = new HttpHeaders();
|
||||||
|
fooHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||||
|
ClassPathResource fooResource = new ClassPathResource("org/springframework/http/codec/multipart/foo.txt");
|
||||||
|
HttpEntity<ClassPathResource> fooPart = new HttpEntity<>(fooResource, fooHeaders);
|
||||||
|
HttpEntity<String> barPart = new HttpEntity<>("bar");
|
||||||
|
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||||
|
parts.add("fooPart", fooPart);
|
||||||
|
parts.add("barPart", barPart);
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RouterFunction<ServerResponse> routerFunction() {
|
||||||
|
MultipartHandler multipartHandler = new MultipartHandler();
|
||||||
|
return route(POST("/"), multipartHandler::handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MultipartHandler {
|
||||||
|
|
||||||
|
public Mono<ServerResponse> handle(ServerRequest request) {
|
||||||
|
return request
|
||||||
|
.body(BodyExtractors.toMultipartData())
|
||||||
|
.flatMap(map -> {
|
||||||
|
Map<String, Part> parts = map.toSingleValueMap();
|
||||||
|
try {
|
||||||
|
assertEquals(2, parts.size());
|
||||||
|
assertEquals("foo.txt", parts.get("fooPart").getFilename().get());
|
||||||
|
assertEquals("bar", parts.get("barPart").getContentAsString().block());
|
||||||
|
}
|
||||||
|
catch(Exception e) {
|
||||||
|
return Mono.error(e);
|
||||||
|
}
|
||||||
|
return ServerResponse.ok().build();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue