使用Spring MVC的ResponseEntity返回一个流

Dav*_*d V 44 java spring servlets spring-mvc

我有一个Spring MVC方法返回一个ResponseEntity.根据检索到的特定数据,有时需要将数据流返回给用户.其他时候它会返回除流之外的其他内容,有时还会返回重定向.我绝对希望这是一个流而不是字节数组,因为它可能很大.

目前,我使用以下代码段返回流:

HttpHeaders httpHeaders = createHttpHeaders();
IOUtils.copy(inputStream, httpServletResponse.getOutputStream());

return new ResponseEntity(httpHeaders, HttpStatus.OK);
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不允许Spring HttpHeaders数据实际填充响应中的HTTP标头.这是有道理的,因为我的代码写入OutputStreamSpring之前收到的ResponseEntity.

这将是非常好的以某种方式返回一个ResponseEntity带有InputStream一个让Spring处理.它也会与我的函数的其他路径并行,我可以成功返回一个ResponseEntity.无论如何,我可以用Spring完成这个任务吗?


此外,我也尝试返回InputStreamResponseEntity只是为了看看春天会接受它.

return new ResponseEntity(inputStream, httpHeaders, HttpStatus.OK);
Run Code Online (Sandbox Code Playgroud)

但它抛出了这个异常:

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Run Code Online (Sandbox Code Playgroud)

我可以通过HttpServletResponse直接设置所有内容来使我的功能工作,但我想只用Spring做到这一点.

Dav*_*d V 95

Spring的InputStreamResource效果很好.您需要手动设置Content-Length,否则Spring会尝试读取流以获取Content-Length.

InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
httpHeaders.setContentLength(contentLengthOfStream);
return new ResponseEntity(inputStreamResource, httpHeaders, HttpStatus.OK);
Run Code Online (Sandbox Code Playgroud)

我从未发现任何建议使用此课程的网页.我只是猜到了,因为我注意到有一些使用ByteArrayResource的建议.

  • 我遇到过这种情况,并且使用这种方法,我可以使用64MB堆流式传输500MB文件 - 它肯定会缓冲输出而不仅仅是分配一个字节数组.非常感谢David V的回答.剩下的唯一问题是:Spring是否正确关闭了InputStream,创建了任何其他资源?我一直试图通过谷歌搜索来解决这个问题,而且我无法以某种方式找到任何信息.我认为*Spring的作者会采取措施避免像这样的代码泄漏资源,但我想知道. (14认同)
  • 正确,默认情况下还会注册一个`ResourceHttpMessageConverter`,它知道如何处理`org.springframework.core.io.Resource`实例.因此将`InputStream`包装在`InputStreamResouce`中并返回它将起作用. (11认同)
  • 它关闭了底层流.https://github.com/spring-projects/spring-framework/blob/v4.3.9.RELEASE/spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java#L117 (6认同)
  • @Nilesh,你需要自己的进程来确定`contentLengthOfStream`.否则Spring将打开流并读取它.对于只能读取一次的大型流或流,这将无法正常工作. (4认同)
  • @SebastiaanvandenBroek,这取决于你如何获得你的流.我们通常从S3读取,因此我们将`contentLengthOfStream`设置为S3 SDK提供的Content-Length值.如果使用文件长度从文件加载,您也可以执行类似的操作. (3认同)
  • 我认为 `Content-Length` 注释仍然相关,似乎[问题已在 Spring 4.1 中修复](https://github.com/spring-projects/spring-framework/issues/16633) [此更改](https://github.com/spring-projects/spring-framework/commit/f0bcb773f9117195ef39acc3b4077c9f41d8afa0)。AFAICT 当您未指定内容时,内容也不会加载到内存中。我使用 Eclipse MAT 检查了下载期间执行的堆转储。我正在流式传输“Clob”,因此如果不读取它,我无法立即访问其大小(以字节为单位)。 (3认同)
  • 如果没有,那就没用了:)。但是是的,它流式传输(以块为单位)内容。您可能需要检查 `ResourceHttpMessageConverter` 的源代码以了解它是如何完成的。 (2认同)
  • 大卫,你是如何解决提前计算内容长度的?我想直接从我的存储库中使用自定义 @Query 进行流式传输。我想我可以先做一个计数查询,但这可能会不同步。 (2认同)