Как отправить данные многостраничной формы с restTemplate Spring-mvc

Я пытаюсь загрузить файл с RestTemplate в Raspberry Pi с помощью Jetty. На Pi запущен сервлет:

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());
}

Я умею это делать с помощью curl

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

Это метод, который должен иметь те же функции в веб-приложении.

@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();
}

Вот результат, который я получаю:

ui-elements.html загружен!

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

1

2

3

Как видите, номер 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.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"])

person Maniek    schedule 09.02.2015    source источник
comment
Хорошо, может быть, у вас есть трассировка стека RestClientException? Не могли бы вы включить его?   -  person Leandro Carracedo    schedule 09.02.2015
comment
У меня нет исключения в консоли, но я обнаружил его во время отладки. Я включу это выше.   -  person Maniek    schedule 10.02.2015
comment
Решение Лоренцо сработало для меня   -  person daddy rocks    schedule 28.01.2020


Ответы (3)


Чтение всего файла в ByteArrayResource может быть проблемой потребления памяти для больших файлов.

Вы можете проксировать загрузку файла в весенний контроллер 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 ...
    }
}
person Lorenzo Polidori    schedule 09.06.2017
comment
Удивительно, но принятый ответ у меня не сработал, но этот сработал! Спасибо. - person manish_s; 04.10.2017
comment
@ lorenzo-polidori Не могли бы вы предоставить пример метода контроллера о том, как получить MultipartInputStreamFileResource? т.е. пример метода контроллера для InputStreamResource - person Krish; 04.12.2017
comment
Я использовал этот отличный подход с некоторыми улучшениями (включая дальнейшее снижение потребления памяти) - и опубликовал здесь продолжение: Потоковая загрузка через @Bean -предоставляется RestTemplateBuilder буферизует полный файл. - person Brent Bradburn; 20.07.2018
comment
Я пробовал этот метод, но у меня ничего не вышло. Я столкнулся с проблемой при отправке запроса POST с данными в многостраничном формате. Вот мой вопрос, можете ли вы помочь мне с решением stackoverflow.com/questions/54429549/ - person Deep Lathia; 01.02.2019
comment
Ты гений. Я не мог заставить ничего работать, но это решение помогло. - person RobOhRob; 28.03.2019
comment
Spring теперь поставляется со своим MultipartFileResource. Подробнее см. мой ответ. - person hzpz; 26.11.2019

Вы получаете исключение, потому что ни один из MessageConverters по умолчанию RestTemplate не знает, как сериализовать InputStream, содержащийся в файле MultipartFile. При отправке объектов через RestTemplate в большинстве случаев вы хотите отправлять POJO. Вы можете исправить это, добавив байты MultipartFile в MultiValueMap вместо самого MultipartFile.

Я думаю, что с вашей частью сервлета тоже что-то не так. Например

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

всегда должен возвращать значение null, поскольку метод getAttribute в ServletRequest не возвращает параметры запроса / формы, а возвращает атрибуты, установленные контекстом сервлета. Вы уверены, что он действительно работает с вашим примером curl?

Вот пример метода Spring MVC, перенаправляющего файл сервлету:

Сервлет (хотя я тестировал его работу в контейнере 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:

@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";
}

Используя их, я могу успешно загрузить файл с помощью метода MVC в сервлет с помощью следующего curl:

curl --form [email protected] localhost:8080/upload/
person JanneK    schedule 16.02.2015
comment
по крайней мере для Spring 3.1 и 3.2 мне также нужно было исправить ошибку в resttemplate при отправке байтовых массивов - stackoverflow.com/questions/4118670/ - person chrismarx; 25.11.2015
comment
Ты наконец спас меня. Да, Spring MVC отлично работает для меня - person Tharsan Sivakumar; 20.09.2016
comment
Я пробовал этот метод, но у меня ничего не вышло. Я столкнулся с проблемой при отправке запроса POST с данными в многостраничном формате. Вот мой вопрос, можете ли вы помочь мне с решением stackoverflow.com/questions/54429549/ - person Deep Lathia; 01.02.2019

Начиная с версии 5.1 Spring Framework поставляется с собственной Resource реализацией для MultipartFiles. Поэтому вы можете упростить ответ Лоренцо, удалив класс MultipartInputStreamFileResource и заполнив карту следующим образом:

[...]

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

[...]
person hzpz    schedule 26.11.2019
comment
Точно, это отлично сработало для меня. Ты спас мне день. Спасибо ! - person Ravindra Ranwala; 02.04.2021