Spring将非多部分文件上传为流

时间:2016-09-28 13:10:16

标签: spring file-upload spring-boot binaryfiles

我正在使用Spring Boot 1.2.5,我想将原始二进制文件上传到控制器。文件大小可能很大,所以我不想将整个请求保存在内存中,而是流式传输文件,实际上文件是作为传输开始生成的,因此客户端甚至不知道文件的大小。我看到了一个如何使用多部分编码文件上传here执行类似操作的示例。但是,我不想要多部分编码上传,只需要一个原始的字节流。我似乎无法在春天找到处理这个用例的方法。

2 个答案:

答案 0 :(得分:4)

我想分享一些可能对某人有帮助的小发现。

我正在使用spring的MultipartFile上传大文件,并且担心spring会将内容存储在内存中。因此,我决定使用getInputStream()方法,希望它将文件直接流式传输到所需位置:

@PostMapping("/upload")
    public ResponseEntity<?> uploadFile(@RequestPart MultipartFile file) throws FileNotFoundException, IOException{

    FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(new File("/storage/upload/", file.getOriginalFilename())));

    return ResponseEntity.ok("Saved");
}

当我用2GB文件测试控制器时,花很长时间才能找到控制器方法。因此,我进行了调试,发现spring / Tomcat首先将文件存储在一个临时文件夹中,然后再处理给控制器。这意味着,当您调用getInputStream()时,它返回一个FileInputStream指向存储在文件系统上的文件,而不是直接从客户端浏览器流式传输。

换句话说,调用FileCopyUtils.copy()很慢,因为它将整个文件复制到另一个位置,然后删除该临时文件,这使得完成请求所花的时间是原来的两倍。

我调查发现,您可以禁用spring功能并手动处理多部分请求,但它有点复杂且容易出错。因此,进一步研究发现,MultipartFile有一种称为transferTo的方法,该方法实际上将临时文件移动到所需位置。我测试了一下,它是瞬时的。我的代码是这样的:

@PostMapping("/upload")
public ResponseEntity<?> uploadFile(@RequestPart MultipartFile file) throws FileNotFoundException, IOException{

    file.transferTo(new File("/storage/upload/", file.getOriginalFilename()));

    return ResponseEntity.ok("Saved");
}

结论,如果您只需要将文件上传到特定目录/文件,则可以使用此解决方案,它的速度将与手动流式传输文件一样快。

重要提示::有两种transferTo()方法,一种方法接收Path,另一种方法接收File。不要使用收到Path的文件,因为它会复制文件并且速度很慢。

EDIT1:

我使用HttpServletRequest测试了该解决方案,但是除非您设置spring config spring.servlet.multipart.enabled = false,否则它仍将存储一个临时文件。使用MultipartHttpServletRequest的解决方案也会发生同样的情况。

使用所发现的解决方案,我看到了三个主要好处:

  1. 很简单
  2. 它允许您一次处理多个文件,只需向控制器方法添加多个@RequestPart MultipartFile
  3. 它使您可以轻松地使用文件处理响应正文
public ResponseEntity<?> uploadFile(@RequestPart @Valid MyCustomPOJO pojo, @RequestPart MultipartFile file1, @RequestPart MultipartFile file2, @RequestPart MultipartFile file3)

这是我为测试某些概念而创建的测试项目的URL,包括以下内容:

https://github.com/noschang/SpringTester

答案 1 :(得分:2)

您可以使用HttpServletRequest输入流。
请注意,如果您有任何预处理请求并使用输入流的过滤器,那么这可能无效。

@ResponseBody
@RequestMapping(path="fileupload", method = RequestMethod.POST, consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void fileUpload(HttpServletRequest request) throws IOException {
    Files.copy(request.getInputStream(), Paths.get("myfilename"));
}