I'm trying to use the Spring Reactive WebClient to upload a file to a spring controller. The controller is really simple and looks like this:
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> uploadFile(
@RequestParam("multipartFile") MultipartFile multipartFile,
@RequestParam Map<String, Object> entityRequest
) {
entityRequest.entrySet().forEach(System.out::println);
System.out.println(multipartFile);
return ResponseEntity.ok("OK");
}
When I use this controller with cURL everything works fine
curl -X POST http://localhost:8080/upload -H 'content-type: multipart/form-data;' -F fileName=test.txt -F randomKey=randomValue -F multipartFile=@document.pdf
The multipartFile goes to the correct parameter and the other parameters go in to the Map.
When I try to do the same from the WebClient I get stuck. My code looks like this:
WebClient client = WebClient.builder().baseUrl("http://localhost:8080").build();
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.set("multipartFile", new ByteArrayResource(Files.readAllBytes(Paths.get("/path/to/my/document.pdf"))));
map.set("fileName", "test.txt");
map.set("randomKey", "randomValue");
String result = client.post()
.uri("/upload")
.contentType(MediaType.MULTIPART_FORM_DATA)
.syncBody(map)
.exchange()
.flatMap(response -> response.bodyToMono(String.class))
.flux()
.blockFirst();
System.out.println("RESULT: " + result);
This results in an 400-error
{
"timestamp":1510228507230,
"status":400,
"error":"Bad Request",
"message":"Required request part 'multipartFile' is not present",
"path":"/upload"
}
Does anyone know how to solve this issue?
You need include a filename to file part to upload success, in combination with
asyncPart()
to avoid buffering all file content, then you can write the code like this:So i found a solution myself. Turns out that Spring really needs the Content-Disposition header to include a filename for a upload to be serialized to a MultipartFile in the Controller.
To do this i had to create a subclass of ByteArrayResource that supports setting the filename
Which can then be used in the client with this code
Using a
ByteArrayResource
in this case is not efficient, as the whole file content will be loaded in memory.Using a
UrlResource
with the"file:"
prefix or aClassPathResource
should solve both issues.Easier way to provide the Content-Disposition