/* * 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.result.method.annotation; import java.time.Duration; import java.util.Map; import java.util.stream.Collectors; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.FilePart; import org.springframework.http.codec.multipart.FormFieldPart; import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.junit.Assert.assertEquals; public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTests { private WebClient webClient; @Override @Before public void setup() throws Exception { super.setup(); this.webClient = WebClient.create("http://localhost:" + this.port); } @Override protected HttpHandler createHttpHandler() { AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); wac.register(TestConfiguration.class); wac.refresh(); return WebHttpHandlerBuilder.webHandler(new DispatcherHandler(wac)).build(); } @Test public void requestPart() { Mono<ClientResponse> result = webClient .post() .uri("/requestPart") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData(generateBody())) .exchange(); StepVerifier .create(result) .consumeNextWith(response -> assertEquals(HttpStatus.OK, response.statusCode())) .verifyComplete(); } @Test public void requestBodyMap() { Mono<String> result = webClient .post() .uri("/requestBodyMap") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData(generateBody())) .retrieve() .bodyToMono(String.class); StepVerifier.create(result) .consumeNextWith(body -> assertEquals("Map[barPart,fooPart]", body)) .verifyComplete(); } @Test public void requestBodyFlux() { Mono<String> result = webClient .post() .uri("/requestBodyFlux") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData(generateBody())) .retrieve() .bodyToMono(String.class); StepVerifier.create(result) .consumeNextWith(body -> assertEquals("Flux[barPart,fooPart]", body)) .verifyComplete(); } @Test public void modelAttribute() { Mono<String> result = webClient .post() .uri("/modelAttribute") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData(generateBody())) .retrieve() .bodyToMono(String.class); StepVerifier.create(result) .consumeNextWith(body -> assertEquals("TestBean[barPart=bar,fooPart=foo.txt]", body)) .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; } @RestController @SuppressWarnings("unused") static class MultipartController { @PostMapping("/requestPart") void requestPart(@RequestPart FormFieldPart barPart, @RequestPart Mono<FilePart> fooPart) { assertEquals("bar", barPart.value()); assertEquals("foo.txt", fooPart.block(Duration.ZERO).filename()); } @PostMapping("/requestBodyMap") Mono<String> requestBodyMap(@RequestBody Mono<MultiValueMap<String, Part>> parts) { return parts.map(map -> map.toSingleValueMap().entrySet().stream() .map(Map.Entry::getKey).sorted().collect(Collectors.joining(",", "Map[", "]"))); } @PostMapping("/requestBodyFlux") Mono<String> requestBodyFlux(@RequestBody Flux<Part> parts) { return parts.map(Part::name).collectList() .map(names -> names.stream().sorted().collect(Collectors.joining(",", "Flux[", "]"))); } @PostMapping("/modelAttribute") String modelAttribute(@ModelAttribute TestBean testBean) { return testBean.toString(); } } static class TestBean { private String barPart; private FilePart fooPart; public String getBarPart() { return this.barPart; } public void setBarPart(String barPart) { this.barPart = barPart; } public FilePart getFooPart() { return this.fooPart; } public void setFooPart(FilePart fooPart) { this.fooPart = fooPart; } @Override public String toString() { return "TestBean[barPart=" + getBarPart() + ",fooPart=" + getFooPart().filename() + "]"; } } @Configuration @EnableWebFlux @SuppressWarnings("unused") static class TestConfiguration { @Bean public MultipartController multipartController() { return new MultipartController(); } } }