Multipart Support
In Spring, the Servlet stack support Multipart by Servlet 3.0+ built-in multipart feature or Apache Commons IO. The new WebFlux stack also add Multipart support.
The Part
presents a part in a multipart/form-data
request, it could be a FilePart
orFormFieldPart
in a browser or any content type outside of browser .
Like the general @Controller
, it accepts a reactive Part
parameter wrapped with Mono
or Flux
.
For example, the following method demonstrates the possible parameters can be processed in WebFlux.
@PostMapping("/requestBodyMap")
Mono<String> requestBodyMap(
@RequestPart("fileParts") FilePart fileParts,
@RequestPart("fileParts") Mono<FilePart> filePartsMono,
@RequestPart("fileParts") Flux<FilePart> filePartsFlux,
@RequestBody Mono<MultiValueMap<String, Part>> partsMono) {
}
For the complete codes, check spring-reactive-sample/multipart.
Now let’s go to a more complex example File upload and download - which is more close to the real world application.
-
Create a Mongo
GridfsTemplate
bean.@Bean public ReactiveGridFsTemplate reactiveGridFsTemplate() throws Exception { return new ReactiveGridFsTemplate(reactiveMongoDbFactory(), mappingMongoConverter()); }
-
Create a
Controller
to handle upload and download.@RestController() @RequestMapping(value = "/multipart") @RequiredArgsConstructor public class MultipartController { private final ReactiveGridFsTemplate gridFsTemplate; @PostMapping("") public Mono<ResponseEntity> upload(@RequestPart Mono<FilePart> fileParts) { return fileParts .flatMap(part -> this.gridFsTemplate.store(part.content(), part.filename())) .map((id) -> ok().body(Map.of("id", id.toHexString()))); } @GetMapping("{id}") public Flux<Void> read(@PathVariable String id, ServerWebExchange exchange) { return this.gridFsTemplate.findOne(query(where("_id").is(id))) .log() .flatMap(gridFsTemplate::getResource) .flatMapMany(r -> exchange.getResponse().writeWith(r.getDownloadStream())); } }
In the above codes,
upload
is used for uploading, thegridFstemplate
store thefilePart
content and return the id to client. Theread
method reads content from Mongo according the provided id, write the content into the web response. -
There is a
MultipartBodyBuilder
can be used to build multipart in client. The following is an example useMultipartBodyBuilder
in testing codes.@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j public class DemoApplicationTests { @LocalServerPort private int port; WebTestClient client; @BeforeEach public void setup() { this.client = WebTestClient.bindToServer() .baseUrl("http://localhost:" + this.port) .build(); } private MultiValueMap<String, HttpEntity<?>> generateBody() { MultipartBodyBuilder builder = new MultipartBodyBuilder(); builder.part("fileParts", new ClassPathResource("/foo.txt", DemoApplicationTests.class)); return builder.build(); } @Test public void testUpload() throws IOException { byte[] result = client .post() .uri("/multipart") .bodyValue(generateBody()) .exchange() .expectStatus().isOk() .expectBody().returnResult().getResponseBody(); ObjectMapper objectMapper = new ObjectMapper(); Map bodyMap = objectMapper.readValue(result, Map.class); String fileId = (String) bodyMap.get("id"); log.debug("updated file id:" + fileId); assertNotNull(fileId); client .get() .uri("/multipart/{id}", fileId) .exchange() .expectStatus().isOk(); } }
For the complete codes, check spring-reactive-sample/multipart-data-mongo and spring-reactive-sample/boot-data-mongo-gridfs.