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
GridfsTemplatebean.
@Bean
public ReactiveGridFsTemplate reactiveGridFsTemplate() throws Exception {
return new ReactiveGridFsTemplate(reactiveMongoDbFactory(), mappingMongoConverter());
}
- Create a
Controllerto 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, the gridFstemplate store the filePart content and return the id to client. The read method reads content from Mongo according the provided id, write the content into the web response.
- There is a
MultipartBodyBuildercan be used to build multipart in client. The following is an example useMultipartBodyBuilderin 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.