I'm trying to create a controller that will consume multipart/mixed requests containing both a json string and a file. I have tried @RequestPart, @RequestParam and using MultipartHttpServletRequest directly, but binding doesn't happen and MultipartHttpServletRequest.getParts() returns an empty collection. However, if the same controller consumes a multipart/form-data request, all approaches work as expected.
Issue https://github.com/spring-projects/spring-boot/issues/30971 mentioned something similar, but it's not the same case and using MultipartHttpServletRequest, as suggested there, doesn't help.
Here's one example using @RequestPart:
@ResponseStatus(HttpStatus.OK)
@PostMapping(
path = "/test/request-part",
consumes = {MediaType.MULTIPART_MIXED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
public void testMultipartMixedWithRequestPart(
@RequestHeader(HttpHeaders.CONTENT_TYPE) String contentType,
@RequestPart(value = "file", required = false) MultipartFile file,
@RequestPart(value = "json", required = false) String json) {
System.out.printf("%s: %s using @RequestPart; file: %s; json: %s%n",
HttpHeaders.CONTENT_TYPE, contentType, file, json);
}
If I send the following request...
curl -H "Content-Type: multipart/mixed" \
-F "json={\"name\": \"test\"};type=application/json" \
-F "file=@dummy-file.txt" \
http://localhost:8080/test/request-part
... the app will print:
Content-Type: multipart/mixed; boundary=------------------------ea524ebe0a53438e using @RequestParam; file: null; json: null
But if multipart/form-data is used instead, it will work as expected:
Content-Type: multipart/form-data; boundary=------------------------a8bb90b20a18542c using @RequestParam; file: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@55d9001c; json: {"name": "test"}
I'm using jdk11 and Spring Boot 2.7.9. The repo below has a minimal application to replicate the issue. https://github.com/andrepnh/spring-multipart-mixed-bug
Please let me know if more information is needed.
Comment From: wilkinsona
I think this may be a cURL bug. When it sends a multipart/mixed request, it sets the Content-Disposition to attachment for both parts:
POST /test/multipart-servlet-request HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.88.1
Accept: */*
Content-Length: 335
Content-Type: multipart/mixed; boundary=------------------------b811af1df53480cb
--------------------------b811af1df53480cb
Content-Disposition: attachment; name="json"
Content-Type: application/json
{"name": "test"}
--------------------------b811af1df53480cb
Content-Disposition: attachment; name="file"; filename="dummy-fi
le.txt"
Content-Type: text/plain
--------------------------b811af1df53480cb--
This content disposition prevents Tomcat from identifying the names of the parts as it looks for form-data:
Content-Type: multipart/mixed; boundary=------------------------b811af1df53480cb using MultipartHttpServletRequest; amount of parts on request: 0
If I modify the request that's sent to using form-data as the content disposition, it works as expected:
POST /test/multipart-servlet-request HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.88.1
Accept: */*
Content-Length: 335
Content-Type: multipart/mixed; boundary=------------------------b811af1df53480cb
--------------------------b811af1df53480cb
Content-Disposition: form-data; name="json"
Content-Type: application/json
{"name": "test"}
--------------------------b811af1df53480cb
Content-Disposition: form-data; name="file"; filename="dummy-fi
le.txt"
Content-Type: text/plain
--------------------------b811af1df53480cb--
Content-Type: multipart/mixed; boundary=------------------------b811af1df53480cb using MultipartHttpServletRequest; amount of parts on request: 2
This appears to be a variation of https://github.com/curl/curl/issues/5256.
If you disagree and believe that cURL's behaviour is correct, please follow up with the Tomcat community as it's code in Tomcat that's responsible for parsing the request.