如何使用多部分/表单和分块编码在 spring mvc 中接收文件上传?

Posted

技术标签:

【中文标题】如何使用多部分/表单和分块编码在 spring mvc 中接收文件上传?【英文标题】:How do I receive a file upload in spring mvc using both multipart/form and chunked encoding? 【发布时间】:2013-12-08 08:57:05 【问题描述】:

我正在尝试编写一个 spring mvc 方法,该方法可以接收多部分/表单或传输编码分块文件上传。我可以编写一个单独的方法来处理每种类型,但我想用相同的方法来做,所以我可以使用相同的 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);

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

谢谢, 史蒂夫

【问题讨论】:

你想要一个端点,并且不介意有两个控制器方法吗?或者你想要单个端点和单个控制器方法? 如果有办法让 2 个控制器方法使用相同的 URI,并且 spring 可以根据是否存在多部分内容来选择调用哪个方法。 【参考方案1】:

摘自我的代码(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 上进行的测试最少。当然可能存在问题,并且可能有一种更简单的提取内容范围的方法,但是 .. 对我有用。

【讨论】:

很棒的解决方案!一旦我完成、签入并清理到足以在此处发布的解决方案,我也会发布我的解决方案。不过你的解决方案更好。 我无法使用第一种方法让 blueimp(使用 AngularJS)上传分块。它总是调用uploadMultipart(通过发送多部分标头)。请问有什么特别的 blueimp 配置可以分享吗? 据我记忆,没什么特别的。你使用的是什么浏览器?将哪些标头发送到服务器端?【参考方案2】:

这是控制器

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

【讨论】:

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

以上是关于如何使用多部分/表单和分块编码在 spring mvc 中接收文件上传?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Spring MockMvc 放置多部分/表单数据?

使用 AJAX + 多部分表单数据 + UTF-8 编码发送文件和文本

禁用传输编码:在 Spring Webflux 响应中分块

Retrofit - @Body 参数不能与表单或多部分编码一起使用

如何使用带有“传输编码:分块”的 winhttp api

多部分和分块协议之间的区别