resttemplate multipart post with InputStreamResource not working [SPR-13571]
See original GitHub issueGreg Adams opened SPR-13571 and commented
I’ve been trying to send a multipart post via restTemplate and have been unable to get it to work with anything but FileSystemResource. In my use case (a weird file-forwarding use case) this forces me to copy a MultiPartFile InputStream into a temp file in order be able to create a FileSystemResource, which seems undesirable.
Here’s a testing version of the file-receiving controller (from another project, running in another servlet container):
@RestController
public class FileReceiveController {
private Log log = LogFactory.getLog(FileReceiveController.class);
@RequestMapping(method = RequestMethod.POST)
public void uploadFile(@RequestParam("customerId") int customerId, @RequestPart("file") MultipartFile multipartFile) {
log.info("customerId: " + customerId);
log.info("Received multipart file - original filename: " + multipartFile.getOriginalFilename());
log.info("content-type: " + multipartFile.getContentType());
log.info("size: " + multipartFile.getSize());
}
}
Here’s the file-forwarding controller:
@RestController
public class FileForwardController {
private RestTemplate restTemplate;
public FileForwardController() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
this.restTemplate = new RestTemplate(requestFactory);
}
@RequestMapping(method = RequestMethod.POST)
public void uploadFile(@RequestParam("customerId") int customerId, @RequestPart("file") MultipartFile multipartFile) {
MultiValueMap<String,Object> parts = new LinkedMultiValueMap<>();
parts.add("customerId", customerId);
try {
// copy to temp file and use FileSystemResource
// File tempFile = File.createTempFile("xyz", "");
// FileCopyUtils.copy(multipartFile.getInputStream(), new FileOutputStream(tempFile));
// parts.add("file", new FileSystemResource(tempFile));
// OR use InputStreamResource (broken)
parts.add("file", new InputStreamResource(multipartFile.getInputStream()));
// OR use ByteArrayResource (broken)
// parts.add("file", new ByteArrayResource(multipartFile.getBytes()));
} catch (IOException e) {
throw new RuntimeException(e);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String,Object>> request = new HttpEntity<>(parts, headers);
restTemplate.exchange("http://localhost:8080", HttpMethod.POST, request, Void.class);
}
}
In this form, the restTemplate.exchange call throws
org.springframework.web.client.HttpClientErrorException: 400 Bad Request
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:614)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:570)
The ByteArrayResource form does the same thing. Only the FileSystemResource form works.
Affects: 4.1.7
Issue Links:
- #18023 Read large data using InputStreamResource at ResourceHttpMessageConverter
- #16633 RestTemplate with InputStreamResource does not work if Content-Length is not set
- #19776 HTTP Response should not contain both Transfer-Encoding and Content-Length headers
- #20990 Consistent treatment of InputStreamResource subclasses
- #21348 Support use of MultipartFile as input to RestTemplate or WebClient
Referenced from: commits https://github.com/spring-projects/spring-framework/commit/27c12809493954a7521819cc623fc7873ecf2f4f
0 votes, 6 watchers
Issue Analytics
- State:
- Created 8 years ago
- Comments:6
Top Related StackOverflow Question
Marcus Schulte commented
Actually
ResourceHttpMessageConverterseems a bit broken to me, when it checks whether it should ask a resource for its content-length by comparing its type to a constant:This gives anyone inheriting from
InputStreamResourcea hard time conforming to the Liskov principle.For me, a combination of a custom-named resource and a slightly altered version of
ResourceHttpMessageConverterdoes the trick:Then, a resource like the following works
Of course, the broken Converter needs to be replaced in the RestTemplate:
A method like
Resource.isContentLengthAvailable()along with corresponding code to support it would be nice to have …Brian Clozel commented
In order to properly write the multipart request, the
FormHttpMessageConverterconfigured automatically with theRestTemplatewill write all parts; if a part inherits from Resource, it calls theResource.getFilename()method to get a file name, see the getFilename() method. If no file name is found, then this part is written as a “regular” part, not a file, in the content-disposition part of the message.In your case, you could do the following:
There are a few ways to improve this situation in the framework.
Resource; the problem is, we don’t have that information and we can only try to guess. This can also lead to new issues, since we’d be considering parts as file whereas those weren’t in the pastMultipartFileResourceimplementation and add it in the reference documentationCould you try the workaround I told you about and confirm this works? Let me know what you think about the solutions listed here.
Thanks!