如何在Spring MVC中使用multipart/form和chunked编码同时接收文件上传?

27

我正在尝试编写一个Spring MVC方法,可以接收multipart/form或transfer-encoding分块文件上传。我可以编写单独的方法来处理每种类型,但我希望使用同一方法来实现,以便可以使用相同的REST POST uri,例如:

我尝试编写一个Spring MVC方法,该方法可以接收multipart/form或transfer-encoding分块文件上传。虽然我可以编写两个不同的方法来处理这两种类型的上传,但我希望能够使用同一个方法。这样,我就可以使用相同的REST POST uri,例如:

http://host:8084/attachments/testupload

这是我目前为止的最佳尝试:

@RequestMapping(value = { "/testupload" }, method = RequestMethod.POST, produces = 
  "application/json")
public @ResponseBody
ResponseEntity<MessageResponseModel> testUpload(
  @RequestParam(value = "filedata", required = false) MultipartFile filedata,
  final HttpServletRequest request) throws IOException {

  InputStream is = null;
  if (filedata == null) {
    is = request.getInputStream();
  }
  else {
    is = filedata.getInputStream();
  }
  byte[] bytes = IOUtils.toByteArray(is);
  System.out.println("read " + bytes.length + " bytes.");

  return new ResponseEntity<MessageResponseModel>(null, null, HttpStatus.OK);
}
使用上述方法可以上传多部分文件,但如果我上传分块文件,Spring会抛出异常,提示如下:
org.springframework.web.multipart.MultipartException: \
The current request is not a multipart request
如果我删除MultipartFile请求参数,它可以很好地处理分块传输编码。如果我保留它,它可以很好地处理MultipartFile上传。如何使用同一方法处理这两种上传类型?
这适用于分块传输编码:
@RequestMapping(value = { "/testupload" }, method = RequestMethod.POST, produces = 
  "application/json")
public @ResponseBody
ResponseEntity<MessageResponseModel> testUpload(
  final HttpServletRequest request) throws IOException {

  InputStream is = null;
  is = request.getInputStream();
  byte[] bytes = IOUtils.toByteArray(is);
  System.out.println("read " + bytes.length + " bytes.");

  return new ResponseEntity<MessageResponseModel>(null, null, HttpStatus.OK);
}

对于 MultipartFile 来说,这个方法非常好用:

@RequestMapping(value = { "/testupload" }, method = RequestMethod.POST, produces = 
  "application/json")
public @ResponseBody
ResponseEntity<MessageResponseModel> testUpload(
  @RequestParam MultipartFile filedata) throws IOException {

  InputStream is = null;
  is = filedata.getInputStream();
  byte[] bytes = IOUtils.toByteArray(is);
  System.out.println("read " + bytes.length + " bytes.");

  return new ResponseEntity<MessageResponseModel>(null, null, HttpStatus.OK);
}

这应该是可行的,有人知道怎么做吗?

谢谢,Steve


你想要一个单一的端点,不介意有两个控制器方法吗?还是你想要一个单一的端点和单一的控制器方法? - gerasalus
如果有一种方法可以拥有两个控制器方法使用相同的URI,并且如果Spring可以根据多部分内容的存在选择调用哪个方法,那么这将起作用。 - Steve L
2个回答

30

以下是我代码的节选(使用Spring 3.2和带有AngularJS的blueimp文件上传):

/**
 * Handles chunked file upload, when file exceeds defined chunked size.
 * 
 * This method is also called by modern browsers and IE >= 10
 */
@RequestMapping(value = "/content-files/upload/", method = RequestMethod.POST, headers = "content-type!=multipart/form-data")
@ResponseBody
public UploadedFile uploadChunked(
        final HttpServletRequest request,
        final HttpServletResponse response) {

    request.getHeader("content-range");//Content-Range:bytes 737280-819199/845769
    request.getHeader("content-length"); //845769
    request.getHeader("content-disposition"); // Content-Disposition:attachment; filename="Screenshot%20from%202012-12-19%2017:28:01.png"
    request.getInputStream(); //actual content.

    //Regex for content range: Pattern.compile("bytes ([0-9]+)-([0-9]+)/([0-9]+)");
    //Regex for filename: Pattern.compile("(?<=filename=\").*?(?=\")");

    //return whatever you want to json
    return new UploadedFile();
}

/**
 * Default Multipart file upload. This method should be invoked only by those that do not
 * support chunked upload.
 * 
 * If browser supports chunked upload, and file is smaller than chunk, it will invoke
 * uploadChunked() method instead.
 * 
 * This is instead a fallback method for IE <=9
 */
@RequestMapping(value = "/content-files/upload/", method = RequestMethod.POST, headers = "content-type=multipart/form-data")
@ResponseBody
public HttpEntity<UploadedFile> uploadMultipart(
        final HttpServletRequest request,
        final HttpServletResponse response,
        @RequestParam("file") final MultipartFile multiPart) {

    //handle regular MultipartFile

    // IE <=9 offers to save file, if it is returned as json, so set content type to plain.
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.TEXT_PLAIN);
    return new HttpEntity<>(new UploadedFile(), headers);
}

这应该可以让你开始。在 IE8、IE9、IE10、Chrome 和 FF 上进行了最小化测试。当然可能会有问题,可能还有更简单的提取内容范围的方法,但是... 对我来说有效。


太棒了!等我完成、检查并整理好我的解决方案后,我也会在这里发布。不过你的解决方案更好。 - Steve L
我无法使用第一种方法让带有AngularJS的blueimp上传分块。它总是调用uploadMultipart(通过发送多部分头)。您是否有任何特殊的blueimp配置可以分享?谢谢。 - Eric B.
就我所记得的,没有什么花哨的东西。你在使用什么浏览器?发送到服务器端的头部信息是什么? - gerasalus

1
这是相关控制器的代码。
package com.faisalbhagat.web.controller;

@Controller
@RequestMapping(value = { "" })
public class UploadController {

    @RequestMapping(value = "/uploadMyFile", method = RequestMethod.POST)
    @ResponseBody
    public String handleFileUpload(MultipartHttpServletRequest request)
            throws Exception {
        Iterator<String> itrator = request.getFileNames();
        MultipartFile multiFile = request.getFile(itrator.next());
                try {
            // just to show that we have actually received the file
            System.out.println("File Length:" + multiFile.getBytes().length);
            System.out.println("File Type:" + multiFile.getContentType());
            String fileName=multiFile.getOriginalFilename();
            System.out.println("File Name:" +fileName);
            String path=request.getServletContext().getRealPath("/");

            //making directories for our required path.
            byte[] bytes = multiFile.getBytes();
            File directory=    new File(path+ "/uploads");
            directory.mkdirs();
            // saving the file
            File file=new File(directory.getAbsolutePath()+System.getProperty("file.separator")+picture.getName());
            BufferedOutputStream stream = new BufferedOutputStream(
                    new FileOutputStream(file));
            stream.write(bytes);
            stream.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new Exception("Error while loading the file");
        }
        return toJson("File Uploaded successfully.")
    }

    public String toJson(Object data)
    {
        ObjectMapper mapper=new ObjectMapper();
        StringBuilder builder=new StringBuilder();
        try {
            builder.append(mapper.writeValueAsString(data));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return builder.toString();
    }
}

您可以在以下链接中找到完整的解决方案,其中包含客户端代码: http://faisalbhagat.blogspot.com/2014/09/springmvc-fileupload-with-ajax-and.html


3
我建议不要使用multiFile.getBytes(),因为它会将整个字节数组加载到内存中。 - XPLOT1ON

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接