当前位置: 首页 > 知识库问答 >
问题:

作为流上传非多部分文件

闾丘高峰
2023-03-14

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

共有3个答案

高嘉树
2023-03-14

要上载不会阻塞MVC请求线程池或占用JVM内存的大型文件,您可以使用以下组合:接受HttpServletRequest(或InputStream),然后在CompletableFuture中使用NIO高效地接收它。

下面是一个示例控制器,让您开始学习。在实际场景中使用此选项之前,您需要确保写入的任何文件名都经过了严格验证。

@RestController
public class TestService {

  @PostMapping("/upload/{filename:.+}")
  public CompletableFuture<ResponseEntity<?>> upload(HttpServletRequest request, @PathVariable("filename") String filename)
      throws ServiceUnavailableException, NotFoundException {

    final int MAX_BUFFER_SIZE = 1024 * 128;

    // TODO: validate 'filename' to ensure it's legal and will be written where you want it
    // to be within the file system. Watch out for the many security gotchas.

    // asynchronously accept the upload

    return CompletableFuture.supplyAsync(() -> {

      try {

        // TODO: Change this to where you want the file to be written
        Path file = Paths.get(filename);

        try (ReadableByteChannel inChannel = Channels.newChannel(request.getInputStream())) {
          try (WritableByteChannel outChannel = Files.newByteChannel(file, CREATE, TRUNCATE_EXISTING, WRITE)) {

            // no way to free a ByteBuffer manually - GC does it
            ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_BUFFER_SIZE);

            while (inChannel.read(buffer) != -1) {
              buffer.flip();
              outChannel.write(buffer);
              buffer.compact();
            }

            // EOF will leave buffer in fill state, flip it and write anything remaining

            buffer.flip();

            while (buffer.hasRemaining()) {
              outChannel.write(buffer);
            }
          }
        }
      } catch (IOException ex) {
        // TODO: log the exception because spring doesn't seem to do that
        throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Failed to upload the file", ex);
      }

      // upload completed successfully

      return ResponseEntity.ok().build();
    });
  }
}
阎德义
2023-03-14

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

我使用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()方法,一个接收路径,另一个接收文件。不要使用接收路径的路径,因为它会复制文件并且速度较慢。

编辑1:

我使用HttpServletRequest测试了这个解决方案,但是它仍然会存储一个临时文件,除非您设置了Spring配置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

卫飞鹏
2023-03-14

您可以只使用HttpServletRequestinputstream
请注意,如果您有任何筛选器预处理请求并使用inputstream,则这可能不起作用。

@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"));
}
 类似资料:
  • 我正在Spring controller中努力实现多部分文件上传。我读过很多问题,谷歌,但似乎什么都不管用。 我明白了 我的BE控制器: FE,angularJS: HTML: 还有应用程序。属性包括: 更新: 当我按照@Byeon0gam的建议从我的控制器中删除@RequestParam时,我不再会遇到这个错误,但是我的文件在控制器中是空的。虽然在FE服务中,如我所见,它不是空的:

  • 我试图创建一个页面,用户可以张贴图像及其细节。现在,当我测试来自postman的spring boot服务时,我能够成功地在服务中获取文件。当我试图从angular5中做同样的事情时,多部分文件在服务中没有被识别,并且总是得到空数组。 我的角服务代码如下 } 我已经尝试添加标头,如multipart/form-data,并将其设置为un定义。无论哪种方式,我都收到了错误。在发布到这里之前,我已经广

  • 我正在使用这个音频记录和视频文件,它是工作的,但我想用OKHTTP取代它。我没弄明白。有人能帮我一下吗?

  • 我试图用python写的lambda (aws)完成多部分上传。下面是我正在使用的代码。大约有120个部件,总大小为30GB。下面的操作似乎没有在5分钟内完成,因此lambda关闭,上传似乎没有完成。S3提供异步多部分上传功能吗?我相信这将确保文件得到合并,不管lambda关闭。

  • 编写代理文件上载的应用程序。我正在使用CURL发布文件,但有一些问题。发布到脚本是正常的,它从脚本发布到下一个服务器就是问题所在。我一直从服务器收到此错误: “请求被拒绝,因为没有找到多部分边界” 这是我的代码: 我在网上读到的所有内容都表明,这应该可行,而且设置标题内容类型也是不必要的,但当我删除内容类型时,我会出现以下错误: 请求不包含多部分/表单数据或多部分/混合流,内容类型标头为null

  • 在Android中使用OKHTTP以多部分方式上传单个大文件(更具体地说,上传到s3)时,我有什么选择?