Sending Multipart File as POST parameters with RestTemplate requests
I am working with Spring 3 and RestTemplate. I have basically, two applications and one of them have to post values to the other app. through rest template.
When the values to post are Strings, it's work perfect, but when i have to post mixed and complex params (like MultipartFiles) i get an converter exception.
As example, i have this:
App1 - PostController:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute UploadDTO pUploadDTO,
BindingResult pResult) throws URISyntaxException, IOException {
URI uri = new URI("http://localhost:8080/app2/file/receiver");
MultiValueMap<String, Object> mvm = new LinkedMultiValueMap<String, Object>();
mvm.add("param1", "TestParameter");
mvm.add("file", pUploadDTO.getFile()); // MultipartFile
Map result = restTemplate.postForObject(uri, mvm, Map.class);
return "redirect:postupload";
}
On the other side... i have another web application (App2) that receives the parameters from the App1.
App2 - ReceiverController
@RequestMapping(value = "/receiver", method = { RequestMethod.POST })
public String processUploadFile(
@RequestParam(value = "param1") String param1,
@RequestParam(value = "file") MultipartFile file) {
if (file == null) {
System.out.println("Shit!... is null");
} else {
System.out.println("Yes!... work done!");
}
return "redirect:postupload";
}
My application-context.xml:
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<bean class="org.springframework.http.converter.FormHttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize">
<value>104857600</value>
</property>
<property name="maxInMemorySize">
<value>4096</value>
</property>
</bean>
Here is the stack of the exception that i am getting when i do the postForObject of the RestTemplate...
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [org.springframework.web.multipart.commons.CommonsMultipartFile]
at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:292)
at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:252)
at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:242)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:194)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:1)
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:588)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:436)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:415)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:294)
at com.yoostar.admintool.web.UploadTestController.create(UploadTestController.java:86)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:175)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:421)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:409)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:774)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.Stand开发者_如何转开发ardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:619)
So my questions are:
- Is it possible to send MultipartFile through RestTemplate using POST?
- Are there some specific converters that I have to use to send this type of objects? I mean is there some MultipartFileHttpMessageConverter to use in my configuration?
A way to solve this without needing to use a FileSystemResource that requires a file on disk, is to use a ByteArrayResource, that way you can send a byte array in your post (this code works with Spring 3.2.3):
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
final String filename="somefile.txt";
map.add("name", filename);
map.add("filename", filename);
ByteArrayResource contentsAsResource = new ByteArrayResource(content.getBytes("UTF-8")){
@Override
public String getFilename(){
return filename;
}
};
map.add("file", contentsAsResource);
String result = restTemplate.postForObject(urlForFacade, map, String.class);
I override the getFilename of the ByteArrayResource because if I don't I get a null pointer exception (apparently it depends on whether the java activation .jar is on the classpath, if it is, it will use the file name to try to determine the content type)
I also ran into the same issue the other day. Google search got me here and several other places, but none gave the solution to this issue. I ended up saving the uploaded file (MultiPartFile) as a tmp file, then use FileSystemResource to upload it via RestTemplate. Here's the code I use,
String tempFileName = "/tmp/" + multiFile.getOriginalFileName();
FileOutputStream fo = new FileOutputStream(tempFileName);
fo.write(asset.getBytes());
fo.close();
parts.add("file", new FileSystemResource(tempFileName));
String response = restTemplate.postForObject(uploadUrl, parts, String.class, authToken, path);
//clean-up
File f = new File(tempFileName);
f.delete();
I am still looking for a more elegant solution to this problem.
I recently struggled with this issue for 3 days. How the client is sending the request might not be the cause, the server might not be configured to handle multipart requests. This is what I had to do to get it working:
pom.xml - Added commons-fileupload dependency (download and add the jar to your project if you are not using dependency management such as maven)
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-version}</version>
</dependency>
web.xml - Add multipart filter and mapping
<filter>
<filter-name>multipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<url-pattern>/springrest/*</url-pattern>
</filter-mapping>
app-context.xml - Add multipart resolver
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize">
<beans:value>10000000</beans:value>
</beans:property>
</beans:bean>
Your Controller
@RequestMapping(value=Constants.REQUEST_MAPPING_ADD_IMAGE, method = RequestMethod.POST, produces = { "application/json"})
public @ResponseBody boolean saveStationImage(
@RequestParam(value = Constants.MONGO_STATION_PROFILE_IMAGE_FILE) MultipartFile file,
@RequestParam(value = Constants.MONGO_STATION_PROFILE_IMAGE_URI) String imageUri,
@RequestParam(value = Constants.MONGO_STATION_PROFILE_IMAGE_TYPE) String imageType,
@RequestParam(value = Constants.MONGO_FIELD_STATION_ID) String stationId) {
// Do something with file
// Return results
}
Your client
public static Boolean updateStationImage(StationImage stationImage) {
if(stationImage == null) {
Log.w(TAG + ":updateStationImage", "Station Image object is null, returning.");
return null;
}
Log.d(TAG, "Uploading: " + stationImage.getImageUri());
try {
RestTemplate restTemplate = new RestTemplate();
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setCharset(Charset.forName("UTF8"));
restTemplate.getMessageConverters().add(formConverter);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAccept(Collections.singletonList(MediaType.parseMediaType("application/json")));
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add(Constants.STATION_PROFILE_IMAGE_FILE, new FileSystemResource(stationImage.getImageFile()));
parts.add(Constants.STATION_PROFILE_IMAGE_URI, stationImage.getImageUri());
parts.add(Constants.STATION_PROFILE_IMAGE_TYPE, stationImage.getImageType());
parts.add(Constants.FIELD_STATION_ID, stationImage.getStationId());
return restTemplate.postForObject(Constants.REST_CLIENT_URL_ADD_IMAGE, parts, Boolean.class);
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
Log.e(TAG + ":addStationImage", sw.toString());
}
return false;
}
That should do the trick. I added as much information as possible because I spent days, piecing together bits and pieces of the full issue, I hope this will help.
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("name 1", "value 1");
parts.add("name 2", "value 2+1");
parts.add("name 2", "value 2+2");
Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
parts.add("logo", logo);
Source xml = new StreamSource(new StringReader("<root><child/></root>"));
parts.add("xml", xml);
template.postForLocation("http://example.com/multipart", parts);
One of our guys does something similar with the filesystemresource. try
mvm.add("file", new FileSystemResource(pUploadDTO.getFile()));
assuming the output of your .getFile is a java File object, that should work the same as ours, which just has a File parameter.
You may simply use MultipartHttpServletRequest
Example:
@RequestMapping(value={"/upload"}, method = RequestMethod.POST,produces = "text/html; charset=utf-8")
@ResponseBody
public String upload(MultipartHttpServletRequest request /*@RequestBody MultipartFile file*/){
String responseMessage = "OK";
MultipartFile file = request.getFile("file");
String param = request.getParameter("param");
try {
System.out.println(file.getOriginalFilename());
System.out.println("some param = "+param);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8));
// read file
}
catch(Exception ex){
ex.printStackTrace();
responseMessage = "fail";
}
return responseMessage;
}
Where parameters names in request.getParameter()
must be same with corresponding frontend names.
Note, that file extracted via getFile()
while other additional parameters extracted via getParameter()
I had to do the same thing that @Luxspes did above..and I am using Spring 4.2.6. Spent quite some time figuring why is ByteArrayResource getting transferred from client to server, but the server is not recognizing it.
ByteArrayResource contentsAsResource = new ByteArrayResource(byteArr){
@Override
public String getFilename(){
return filename;
}
};
If you have to send a multipart file that is composed, among other things, by an Object that needs to be converted with a specific HttpMessageConverter and you get the "no suitable HttpMessageConverter" error no matter what you try, you may want to try with this:
RestTemplate restTemplate = new RestTemplate();
FormHttpMessageConverter converter = new FormHttpMessageConverter();
converter.addPartConverter(new TheRequiredHttpMessageConverter());
//for example, in my case it was "new MappingJackson2HttpMessageConverter()"
restTemplate.getMessageConverters().add(converter);
This solved the problem for me with a custom Object that, together with a file (instanceof FileSystemResource, in my case), was part of the multipart file I needed to send. I tried with TrueGuidance's solution (and many others found around the web) to no avail, then I looked at FormHttpMessageConverter's source code and tried this.
You have to add the FormHttpMessageConverter to your applicationContext.xml to be able to post multipart files.
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.FormHttpMessageConverter" />
</list>
</property>
</bean>
See http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html for examples.
I had this issue and found a much simpler solution than using a ByteArrayResource.
Simply do
public void loadInvoices(MultipartFile invoices, String channel) throws IOException {
init();
Resource invoicesResource = invoices.getResource();
LinkedMultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("file", invoicesResource);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
httpHeaders.set("channel", channel);
HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(parts, httpHeaders);
String url = String.format("%s/rest/inbound/invoices/upload", baseUrl);
restTemplate.postForEntity(url, httpEntity, JobData.class);
}
It works, and no messing around with the file system or byte arrays.
精彩评论