Maniek Maniek - 28 days ago 30
Java Question

How to send Multipart form data with restTemplate Spring-mvc

I am trying to upload a file with RestTemplate to Raspberry Pi with Jetty. On Pi there is a servlet running:

protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

PrintWriter outp = resp.getWriter();

StringBuffer buff = new StringBuffer();

File file1 = (File) req.getAttribute("userfile1");
String p = req.getParameter("path");
boolean success = false;

if (file1 == null || !file1.exists()) {
buff.append("File does not exist\n");
} else if (file1.isDirectory()) {
buff.append("File is a directory\n");
} else {
File outputFile = new File(req.getParameter("userfile1"));
if(isValidPath(p)){
p = DRIVE_ROOT + p;
final File finalDest = new File(p
+ outputFile.getName());
success = false;
try {
copyFileUsingFileChannels(file1, finalDest);
finalDest.setWritable(true);
success = true;
} catch (Exception e) {
e.printStackTrace();
}
if (success){
buff.append("File successfully uploaded.\n");
}
else{
buff.append("Failed to save file.");
}
}
else{
buff.append("Invalid path.\n");
}
}
outp.write(buff.toString());
}


I am able to successfully do it with curl

curl --form userfile1=@/home/pi/src/CreateNewFolderServlet.java --form press=OK localhost:2222/pi/GetFileServlet?path="/media/"


This is the method that is supposed to have the same functionality on webapp.

@ResponseBody
@RequestMapping(value="/upload/",method=RequestMethod.POST ,produces = "text/plain")
public String uploadFile(MultipartHttpServletRequest request2, HttpServletResponse response2){

Iterator<String> itr = request2.getFileNames();

MultipartFile file = request2.getFile(itr.next());
System.out.println(file.getOriginalFilename() +" uploaded!");

System.out.println(file.toString());
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("userfile1",file);
//reqEntity.addPart("userfile1", file);
String path="/public/";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
System.out.println("1");
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(parts, headers);
String url = url2+"/pi/GetFileServlet?path="+path;
System.out.println("2");
/* restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
restTemplate.getMessageConverters().add(
new MappingJackson2HttpMessageConverter());*/
System.out.println("3");
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request,String.class);
System.out.println("4");
System.out.println("response : " +response);
if(response==null||response.getBody().trim()==""){
return "error";
}
return response.getBody();
}


This is the output that I get:

ui-elements.html uploaded!

org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@47e7673e

1

2

3

As you can see number 4 is not printed
No exception in console.
Exceptions found during debugging:

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.web.multipart.support.StandardMultipartFile["inputStream"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.web.multipart.support.StandardMultipartFile["inputStream"])

Answer

You are getting the exception because none of RestTemplate's default MessageConverters know how to serialize the InputStream contained by the MultipartFile file. When sending objects via RestTemplate, in most cases you want to send POJOs. You can fix this by adding the bytes of the MultipartFile to the MultiValueMap instead of the MultipartFile itself.

I think there is also something wrong with your servlet part. For instance

File file1 = (File) req.getAttribute("userfile1");

should always return null, as ServletRequest's getAttribute method does not return request/form parameters but attributes set by the servlet context. Are you sure it is actually working with your curl example?

Here is an example of a Spring MVC method forwarding a file to a servlet:

Servlet (though I tested it running in a Spring MVC container), adapted from here:

@RequestMapping("/pi")
private void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

  final String path = request.getParameter("destination");
  final Part filePart = request.getPart("file");
  final String fileName = request.getParameter("filename");

  OutputStream out = null;
  InputStream fileContent = null;
  final PrintWriter writer = response.getWriter();

  try {
    out = new FileOutputStream(new File(path + File.separator
            + fileName));
    fileContent = filePart.getInputStream();

    int read = 0;
    final byte[] bytes = new byte[1024];

    while ((read = fileContent.read(bytes)) != -1) {
      out.write(bytes, 0, read);
    }
    writer.println("New file " + fileName + " created at " + path);

  } catch (FileNotFoundException fne) {
    writer.println("You either did not specify a file to upload or are "
            + "trying to upload a file to a protected or nonexistent "
            + "location.");
    writer.println("<br/> ERROR: " + fne.getMessage());

  } finally {
    if (out != null) {
      out.close();
    }
    if (fileContent != null) {
      fileContent.close();
    }
    if (writer != null) {
      writer.close();
    }
  }
}

Spring MVC method:

@ResponseBody
@RequestMapping(value="/upload/", method=RequestMethod.POST, 
        produces = "text/plain")
public String uploadFile(MultipartHttpServletRequest request) 
        throws IOException {

  Iterator<String> itr = request.getFileNames();

  MultipartFile file = request.getFile(itr.next());
  MultiValueMap<String, Object> parts = 
          new LinkedMultiValueMap<String, Object>();
  parts.add("file", new ByteArrayResource(file.getBytes()));
  parts.add("filename", file.getOriginalFilename());

  RestTemplate restTemplate = new RestTemplate();
  HttpHeaders headers = new HttpHeaders();
  headers.setContentType(MediaType.MULTIPART_FORM_DATA);

  HttpEntity<MultiValueMap<String, Object>> requestEntity =
          new HttpEntity<MultiValueMap<String, Object>>(parts, headers);

  // file upload path on destination server
  parts.add("destination", "./");

  ResponseEntity<String> response =
          restTemplate.exchange("http://localhost:8080/pi", 
                  HttpMethod.POST, requestEntity, String.class);

  if (response != null && !response.getBody().trim().equals("")) {
    return response.getBody();
  }

  return "error";
}

Using these I can succesfully upload a file through the MVC method to the servlet by the following curl:

curl --form file=@test.dat localhost:8080/upload/
Comments