在Spring Boot中流式传输/下载文件而不保留在内存中



我的应用程序使用Google Cloud Storage存储大文件,并使用Spring Boot作为后端。

我刚刚完成了负责下载的控制器的实现,遵循了我认为可能是正确的,但我从浏览器中得到的行为并不是我所期望的。

步骤

如果我尝试下载一个文件,会发生以下情况:

  1. 使用ReadChannel从谷歌云存储读取文件("blob")
  2. 通道写入ByteArrayOutputStream
  3. 通道使用缓冲区写入
  4. ByteArrayOutputStream在InputStreamResource中转换
  5. 然后在ResponseEntity中传递inputStream资源

意外行为

如果用户去下载的控制器,并要求一个特定的文件,该文件首先被完全加载到服务器上,然后提供给客户端。同时,客户端像服务器一样看到";没有响应";(因为服务器正在加载文件),这并不好。当文件被完全加载到服务器上时;下载的";真的很快。

我想要的是避免";没有响应";部分,并将文件流式传输到客户端,就像通常情况下当你尝试下载一个大文件时一样,你会看到一个";下载";在Chrome中滚动文件。

如果您能理解以下代码的错误,我们将不胜感激!

Sring控制器的代码如下:

@RequestMapping("/**/*")
public ResponseEntity<InputStreamResource> downloadFile(
RedirectAttributes redirectAttributes, HttpServletRequest request, HttpServletResponse response) throws Exception {
String fid = ....
WritableByteChannel channel;
Storage storage = new DownloadAction().getStorage();
BlobId blobId = BlobId.of(BUCKET_ID, blobPath);
Blob blob = storage.get(blobId);

ByteArrayOutputStream writeTo = new ByteArrayOutputStream();
try (ReadChannel reader = blob.reader()) {
channel = Channels.newChannel(writeTo);
ByteBuffer bytes = ByteBuffer.allocate(64 * 1024);
while (reader.read(bytes) > 0) {
bytes.flip();
channel.write(bytes);
bytes.clear();
}
}
channel.close();
writeTo.close();
InputStream tmp = new ByteArrayInputStream(writeTo.toByteArray());
InputStreamResource resource = new InputStreamResource(tmp);
MediaType mediaType = MediaType.parseMediaType(mimeType);
return ResponseEntity.ok()
.contentType(mediaType)
.contentLength(fileSize)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="" + fileName + """)
.body(resource);
} catch (IOException e) {
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}

根据您的问题,您的bucket中的对象是私有的,您希望为有限的人群提供对它们的访问权限。如果是这样,签名的URL就是您所需要的。

您可以为bucket中的特定对象生成签名的URL,并将用户重定向到生成的URL,这样他就可以自己下载该文件。

更多信息:GCloud签名URL

最新更新