使用RestTemplate获取InputStream

use*_*349 28 java inputstream resttemplate

我正在使用URL类从中读取InputStream.有什么方法可以使用RestTemplate吗?

InputStream input = new URL(url).openStream();
JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName())); 
Run Code Online (Sandbox Code Playgroud)

我如何InputStream 使用RestTemplate而不是使用URL

Abh*_*kar 33

以前的答案并没有错,但它们并没有深入到我喜欢的深度.有些情况下处理低级别InputStream不仅是可取的,而且是必要的,最常见的例子是将大型文件从源(某些Web服务器)流式传输到目标(数据库).如果您尝试使用a ByteArrayInputStream,那么您将会受到欢迎,并不会令人惊讶OutOfMemoryError.是的,您可以滚动自己的HTTP客户端代码,但是您必须处理错误的响应代码,响应转换器等.如果您已经在使用Spring,那么寻找RestTemplate是一个很自然的选择.

在撰写本文时,spring-web:5.0.2.RELEASE有一个ResourceHttpMessageConverter具有a boolean supportsReadStreaming,如果设置,并且响应类型为InputStreamResource,则返回InputStreamResource; 否则它返回一个ByteArrayResource.很明显,你并不是唯一一个要求流媒体支持的人.

但是,有一个问题:RestTemplateHttpMessageConverter运行后很快关闭响应.因此,即使您要求InputStreamResource并获得它也没有用,因为响应流已经关闭.我认为这是一个他们忽略的设计缺陷; 它应该依赖于响应类型.所以不幸的是,对于阅读,你必须完全消费反应; 如果使用你就无法传递它RestTemplate.

写作没有问题.如果你想流式传输InputStream,ResourceHttpMessageConverter将为你做.在内部,它使用org.springframework.util.StreamUtils在从一个时间写4096个字节InputStreamOutputStream.

一些HttpMessageConverter支持所有媒体类型,因此根据您的要求,您可能必须从中删除默认类型RestTemplate,并设置您需要的那些,注意它们的相对排序.

最后但并非最不重要,实现ClientHttpRequestFactoryboolean bufferRequestBody,你可以,也应该设置为false你上载大量流.否则,你知道,OutOfMemoryError.在撰写本文时,SimpleClientHttpRequestFactory(JDK客户端)和HttpComponentsClientHttpRequestFactory(Apache HTTP客户端)支持此功能,但不支持OkHttp3ClientHttpRequestFactory.再次,设计监督.

编辑:提交的票据SPR-16885.

  • @Kieveli我不知道您的意思是什么。我提供了对诸如流之类的实际用例的引用。勺子喂食代码不是此答案的目标。 (2认同)

Abd*_*ull 25

春天有一个org.springframework.http.converter.ResourceHttpMessageConverter.它转换了Spring的org.springframework.core.io.Resource课程.那Resource类封装InputStream,您可以通过获得someResource.getInputStream().

把这个都在一起,其实你可以得到一个InputStream通过RestTemplate外的开箱通过指定Resource.class为您RestTemplate调用的响应类型.

下面是使用的一个示例RestTemplateexchange(..)方法:

import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.core.io.Resource;

ResponseEntity<Resource> responseEntity = restTemplate.exchange( someUrlString, HttpMethod.GET, someHttpEntity, Resource.class );

InputStream responseInputStream;
try {
    responseInputStream = responseEntity.getBody().getInputStream();
}
catch (IOException e) {
    throw new RuntimeException(e);
}

// use responseInputStream
Run Code Online (Sandbox Code Playgroud)

  • 基础InputStream将是ByteArrayStream。这意味着响应主体将被加载到内存中。 (4认同)
  • `responseEntity.getBody()。getInputStream();`不正确。没有getInputStream方法。 (3认同)

Ita*_*rBe 7

我遇到了同样的问题,并通过扩展 RestTemplate 并仅在读取流后关闭连接来解决它。

你可以在这里看到代码:https : //github.com/ItamarBenjamin/stream-rest-template


Sot*_*lis 6

您不应该InputStream直接获得。RestTemplate旨在封装处理响应(和请求)内容。它的优势在于处理所有IO,并为您提供现成的Java对象。

您需要注册适当的HttpMessageConverter对象。这些对象将可以InputStream通过HttpInputMessage对象访问响应的。

作为Abdull表明,Spring并配备了HttpMessageConverter用于实现Resource其自身的包装InputStreamResourceHttpMessageConverter。它不支持所有Resource类型,但是由于无论如何都应该对接口进行编程,因此应该只使用superinterface Resource

当前的实现(4.3.5)将返回ByteArrayResource,并将响应流的内容复制到ByteArrayInputStream可以访问的新内容。

您不必关闭流。在RestTemplate你需要照顾那个。(这很不幸,如果您尝试使用InputStreamResource,则支持的另一种类型ResourceHttpMessageConverter,因为它包装了底层的响应,InputStream但是在可以公开给客户代码之前已被关闭。)

  • “您不应该直接获取 InputStream。RestTemplate 旨在封装处理响应(和请求)内容。它的优势在于处理所有 IO 并为您提供一个现成的 Java 对象。” 绝对错误的。在某些情况下,序列化对象比堆可以容纳的大得多,因此您**必须**流式传输其序列化,而不是将其转换为字符串或您使用的任何序列化介质,然后才将其写入缓冲区中输出流。 (3认同)
  • @Dragas 一般来说,您对“InputStream”的看法并没有错。然而,“RestTemplate”并不是为支持流媒体用例而设计的。您可以在[此处](https://github.com/spring-projects/spring-framework/issues/21424#issuecomment-453472592)看到其原作者之一(Brian Clozel)的评论:_`RestTemplate`是不意味着传输响应主体;它的合同不允许这样做,而且它已经存在了很长时间,以至于在不中断许多应用程序的情况下改变其行为的基本部分是不可能完成的。_ (2认同)

val*_*jon 6

非常简单但有效的解决方案是使用ResponseExtractor. 当您想要对非常大的输入流进行操作并且您的 RAM 有限时,它特别有用。

以下是您应该如何实施它:

public void consumerInputStreamWithoutBuffering(String url, Consumer<InputStream> streamConsumer) throws IOException {

    final ResponseExtractor responseExtractor =
            (ClientHttpResponse clientHttpResponse) -> {
                streamConsumer.accept(clientHttpResponse.getBody());
                return null;
            };

    restTemplate.execute(url, HttpMethod.GET, null, responseExtractor);
}
Run Code Online (Sandbox Code Playgroud)

然后,在您需要的任何地方调用该方法:

Consumer<InputStream> doWhileDownloading = inputStream -> {
                //Use inputStream for your business logic...
};

consumerInputStreamWithoutBuffering("https://localhost.com/download", doWhileDownloading);
Run Code Online (Sandbox Code Playgroud)

请注意以下常见陷阱

public InputStream getInputStreamFromResponse(String url) throws IOException {

    final ResponseExtractor<InputStream> responseExtractor =
            clientHttpResponse -> clientHttpResponse.getBody();

    return restTemplate.execute(url, HttpMethod.GET, null, responseExtractor);
}
Run Code Online (Sandbox Code Playgroud)

InputStream在您可以访问之前,此处将关闭


Spr*_*ing 5

我就是这样做解决的。希望对大家有所帮助。

    @GetMapping("largeFile")
    public ResponseEntity<InputStreamResource> downloadLargeFile(
            @RequestParam("fileName") String fileName
    ) throws IOException {

        RestTemplate restTemplate = new RestTemplate();

        // Optional Accept header
        RequestCallback requestCallback = request -> request.getHeaders()
                .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));

        // Streams the response instead of loading it all in memory
        ResponseExtractor<InputStreamResource> responseExtractor = response -> {
            // Here I write the response to a file but do what you like
            Path path = Paths.get("tmp/" + fileName);
            Files.copy(response.getBody(), path, StandardCopyOption.REPLACE_EXISTING);
            return new InputStreamResource(new FileInputStream(String.format("tmp/%s", fileName)));
        };

        InputStreamResource response = restTemplate.execute(
            String.format("http://%s:%s/file/largeFileRestTemplate?fileName=%s", host, "9091", fileName),
            HttpMethod.GET,
            requestCallback,
            responseExtractor
        );

        return ResponseEntity
            .ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", fileName))
            .body(response);
    }
Run Code Online (Sandbox Code Playgroud)

  • 它被加载到内存中,但并不意味着全部容量。它通过流逐个字节加载。 (4认同)