When trying to send a multipart request over http interfaces, multipart is sent directly instead of resource and causing the following on error on server side.
org.apache.tomcat.util.http.fileupload.MultipartStream$MalformedStreamException: Stream ended unexpectedly
at org.apache.tomcat.util.http.fileupload.MultipartStream.readHeaders(MultipartStream.java:520) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.findNextItem(FileItemIteratorImpl.java:243) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.<init>(FileItemIteratorImpl.java:142) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:252) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:276) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
at org.apache.catalina.connector.Request.parseParts(Request.java:2790) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
at org.apache.catalina.connector.Request.getParts(Request.java:2691) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:774) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
And on the client side after error, trying to serialize multipart and getting the jackson error.
With a custom resolver or by changing interface parameter from multipart to resource problem can be solved but a built in support would be awesome. Example for client: https://github.com/oguder/spring-webclient-multipart
Server side is pretty straightforward.
@PostMapping("/test")
public String test(@RequestPart(name = "file") MultipartFile file, @RequestPart(name = "info") AdditionalInfo additionalInfo) {
return String.format("file name = %s and size %d KB additional name = %s", file.getOriginalFilename(), file.getSize() / 1024, additionalInfo.getName());
}
Comment From: rstoyanchev
I suppose if you're on the server side, you may have a MultipartFile
already and it's convenient to pass that in. We could update RequestPartArgumentResolver
along those lines. However, wouldn't it make more sense for the HTTP client interface to declare Resource
anyway. It's more generally applicable and not limited to server-side usage by requiring a MultipartFile
input. Your controller method would simply get the resource from the MultipartFile
:
@PostMapping("/test")
public String test(@RequestPart(name = "file") MultipartFile file, @RequestPart(name = "info") AdditionalInfo additionalInfo) {
return testApi.test(file.getResource(), additionalInfo);
}
Comment From: oguder
We have a case interfaces are in library and client can not modify. We were able to handle it with a custom resolver but custom resolvers are more prone to bugs compared to libraries. That is why, I have created the request.
Comment From: rstoyanchev
Adding support for MultipartFile
in MultipartBodyBuilder
would address this and also be more broadly helpful.
Comment From: amand1996
I would like to pick this up if no one has picked it up.
Comment From: OlgaMaciaszek
@amand1996 thanks, we're working on it already.
Comment From: rstoyanchev
On closer look, MultipartBodyBuilder
is in a lower level package than MultipartFile
and therefore can't refer to it. We'll add an argument resolver instead.
Comment From: rstoyanchev
Superseded by #30728.
Comment From: MarkusRohlof
For anyone else stranding here and wondering what to make of this: Starting with Spring Boot 3.2.0-M1 (or just spring-web 6.1.0-M2) you can now have a PostExchange with a MultipartFile like this:
@PostExchange("/images")
fun uploadImage(
img: MultipartFile
)
It seems that specifying the contentType
in PostExchange
is at least unnecessary, while annotating the MultipartFile
as @RequestPart
leads to an Exception because the wrong ArgumentResolver tries to resolve the Argument.
Comment From: rstoyanchev
Thanks for the feedback, @MarkusRohlof. I've created #31139 to address the exception you mentioned.