Java 如何使用restTemplate Spring-mvc发送多部分表单数据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28408271/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 06:11:04  来源:igfitidea点击:

How to send Multipart form data with restTemplate Spring-mvc

javaspringpostmultipartform-dataresttemplate

提问by Maniek

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

我正在尝试使用 Jetty 将带有 RestTemplate 的文件上传到 Raspberry Pi。在 Pi 上有一个 servlet 正在运行:

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 成功地做到这一点

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

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.

这是应该在 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 MappingHymanson2HttpMessageConverter());*/
    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!

ui-elements.html 上传!

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

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

1

1

2

2

3

3

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

如您所见,未打印数字 4 控制台中没有异常。调试过程中发现的异常:

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.Hymanson.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"])

采纳答案by Lorenzo Polidori

Reading the whole file in a ByteArrayResourcecan be a memory consumption issue with large files.

在 a 中读取整个文件ByteArrayResource可能是大文件的内存消耗问题。

You can proxy a file upload in a spring mvc controller using a InputStreamResource:

您可以使用以下命令在 spring mvc 控制器中代理文件上传InputStreamResource

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResponseEntity<?> uploadImages(@RequestPart("images") final MultipartFile[] files) throws IOException {
    LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
    String response;
    HttpStatus httpStatus = HttpStatus.CREATED;

    try {
        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                map.add("images", new MultipartInputStreamFileResource(file.getInputStream(), file.getOriginalFilename()));
            }
        }

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

        String url = "http://example.com/upload";

        HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
        response = restTemplate.postForObject(url, requestEntity, String.class);

    } catch (HttpStatusCodeException e) {
        httpStatus = HttpStatus.valueOf(e.getStatusCode().value());
        response = e.getResponseBodyAsString();
    } catch (Exception e) {
        httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        response = e.getMessage();
    }

    return new ResponseEntity<>(response, httpStatus);
}

class MultipartInputStreamFileResource extends InputStreamResource {

    private final String filename;

    MultipartInputStreamFileResource(InputStream inputStream, String filename) {
        super(inputStream);
        this.filename = filename;
    }

    @Override
    public String getFilename() {
        return this.filename;
    }

    @Override
    public long contentLength() throws IOException {
        return -1; // we do not want to generally read the whole stream into memory ...
    }
}

回答by JanneK

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.

您收到异常是因为 RestTemplate 的默认 MessageConverters 都不知道如何序列化 MultipartFile 文件包含的 InputStream。当通过 RestTemplate 发送对象时,大多数情况下您希望发送 POJO。您可以通过将 MultipartFile 的字节添加到 MultiValueMap 而不是 MultipartFile 本身来解决此问题。

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

我认为您的 servlet 部分也有问题。例如

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?

应该总是返回 null,因为 ServletRequest 的 getAttribute 方法不返回请求/表单参数,而是由 servlet 上下文设置的属性。您确定它确实适用于您的 curl 示例吗?

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

下面是一个 Spring MVC 方法将文件转发到 servlet 的示例:

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

Servlet(虽然我测试了它在 Spring MVC 容器中运行),改编自这里

@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:

Spring MVC 方法:

@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 成功地通过 MVC 方法将文件上传到 servlet:

curl --form [email protected] localhost:8080/upload/

回答by hzpz

Since version 5.1 the Spring Framework ships with its own Resourceimplementation for MultipartFiles. You can therefore simplify Lorenzo's answerby removing the MultipartInputStreamFileResourceclass and filling the map as follows:

从 5.1 版开始,Spring 框架附带了自己ResourceMultipartFiles实现。因此,您可以通过删除类并按如下方式填充地图来简化Lorenzo 的答案MultipartInputStreamFileResource

[...]

for (MultipartFile file : files) {
    if (!file.isEmpty()) {
        map.add("images", file.getResource());
    }
}

[...]