【问题标题】:How to forward large files with RestTemplate?如何使用 RestTemplate 转发大文件?
【发布时间】:2013-03-24 19:07:01
【问题描述】:

我有一个网络服务调用,通过它可以上传 zip 文件。然后将文件转发到另一个服务进行存储、解压缩等。 现在文件存储在文件系统上,然后构建 FileSystemResource。

Resource zipFile = new FileSystemResource(tempFile.getAbsolutePath());

我可以使用 ByteStreamResource 来节省时间(在转发之前不需要将文件保存在磁盘上),但为此我需要构建一个字节数组。如果文件很大,我会收到“OutOfMemory : java heap space”错误。

ByteArrayResource r = new ByteArrayResource(inputStream.getBytes());

任何使用 RestTemplate 转发文件而不出现 OutOfMemory 错误的解决方案?

【问题讨论】:

  • 您不能将输入流传递给其他服务吗?或者您必须将输入流写入文件,然后将文件句柄传递给服务。另外,不确定这与 Groovy 有何关系?
  • 我没有找到任何方法来传递输入流。我使用了 Groovy 标记,因为代码在 groovy 中(java InputStream 没有 getBytes 方法)
  • 啊,当你用 Java 风格编写它时,我被抛出了 ;-) 那么这个其他服务接受什么?
  • 我没有提供这个作为答案,因为它的范围比您的问题的答案要大一些,但是您是否考虑过 Spring Integration 来解决这个问题?您基本上是在查看具有 Web 服务和 REST 适配器的声明检查模式。您可以通过 SI 框架为您完成很多工作。 static.springsource.org/spring-integration/reference/htmlsingle/…

标签: java spring groovy resttemplate


【解决方案1】:

编辑:其他答案更好(使用Resourcehttps://stackoverflow.com/a/36226006/116509

我原来的答案:

您可以使用execute 进行这种低级操作。在这个 sn-p 中,我使用了 Commons IO 的 copy 方法来复制输入流。您需要自定义 HttpMessageConverterExtractor 以获得您期望的响应类型。

final InputStream fis = new FileInputStream(new File("c:\\autoexec.bat")); // or whatever
final RequestCallback requestCallback = new RequestCallback() {
     @Override
    public void doWithRequest(final ClientHttpRequest request) throws IOException {
        request.getHeaders().add("Content-type", "application/octet-stream");
        IOUtils.copy(fis, request.getBody());
     }
};
final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);     
restTemplate.setRequestFactory(requestFactory);     
final HttpMessageConverterExtractor<String> responseExtractor =
    new HttpMessageConverterExtractor<String>(String.class, restTemplate.getMessageConverters());
restTemplate.execute("http://localhost:4000", HttpMethod.POST, requestCallback, responseExtractor);

(感谢 Baz 指出您需要致电 setBufferRequestBody(false) 否则会失败)

【讨论】:

  • 输入流不都是这样加载到内存中的吗?
  • @Bax 不,它会在复制过程中逐步加载。应该非常节省内存。
  • @Bax doWithRequest 在匿名类中;它是之前定义的,但不会被调用,直到执行(它是一个回调)
  • 找到了一种解决方案,使用 *ClientHttpRequestFactory#setBufferRequestBody(false) 依次生成一个 *StreamingClientHttpRequest,它在调用 getBody() 时打开连接。
  • 啊... autoexec.bat。谢谢你的回忆
【解决方案2】:

您真正需要的@artbristol 的answer 的唯一部分是这个(您可以将其设置为RestTemplate Spring bean):

final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);     
restTemplate.setRequestFactory(requestFactory);     

在那之后,我认为只使用 FileSystemResource 作为您的请求正文会做正确的事情。

我也以这种方式成功使用了InputStreamResource,适用于您已经将数据作为InputStream 并且不需要多次使用它的情况。

在我的例子中,我们已经 gzip 压缩了我们的文件并将 GZipInputStream 包装在 InputStreamResource 中。

【讨论】:

  • 这解决了我原来的问题,其余模板能够处理大文件而不会耗尽内存非常感谢!之后我开始收到Error writing body to server 并且我能够通过this post 中的响应解决这个问题。我把这个留在这里,以防其他人面临同样的问题。谢谢!
【解决方案3】:

我认为上面的答案有不必要的代码 - 你不需要做一个匿名的 RequestCallback 内部类,你不需要使用 apache 的 IOUtils。

我花了一些时间研究与您类似的解决方案,这就是我想出的:

您可以使用the Spring Resource Interface 和RestTemplate 更简单地实现您的目标。

RestTemplate restTemplate = new RestTemplate();

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);

File file = new File("/whatever");

HttpEntity<FileSystemResource> requestEntity = new HttpEntity<>(new FileSystemResource(file));
ResponseEntity e = restTemplate.exchange("http://localhost:4000", HttpMethod.POST, requestEntity, Map.class);

(此示例假设您发布到的响应是 JSON。但是,这可以通过更改返回类型类来轻松更改...设置为上面的 Map.class)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-28
    • 2020-11-17
    • 1970-01-01
    • 1970-01-01
    • 2019-08-03
    • 2017-09-25
    • 1970-01-01
    • 2012-12-18
    相关资源
    最近更新 更多