Java 使用RestTemplate获取InputStream

Java 使用RestTemplate获取InputStream,java,inputstream,resttemplate,Java,Inputstream,Resttemplate,我正在使用URL类从中读取InputStream。有什么方法可以使用RestTemplate进行此操作吗 InputStream input = new URL(url).openStream(); JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName())); 如何使用restemplate而不是使用URL获取InputStream?您不应该直接获

我正在使用URL类从中读取InputStream。有什么方法可以使用RestTemplate进行此操作吗

InputStream input = new URL(url).openStream();
JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName())); 

如何使用
restemplate
而不是使用
URL
获取
InputStream

您不应该直接获取
InputStream
RestTemplate
用于封装处理响应(和请求)内容。它的优势在于处理所有IO并为您提供一个随时可用的Java对象

restemplate
的原始作者之一Brian Clozel有:

RestTemplate
不用于流式传输响应主体;它的合同 这是不允许的,而且它已经存在很长时间了,以至于变化如此之大 它的行为的一个基本部分不可能不中断许多活动 应用程序

您需要注册适当的对象。它们将通过对象访问响应的
InputStream

,Spring为
资源
提供了一个
HttpMessageConverter
实现,它本身封装了一个
输入流
。它不支持所有的
资源
类型,但是由于您无论如何都应该对接口进行编程,因此您应该只使用superinterface
资源

当前实现(4.3.5)将返回一个,其中响应流的内容复制到一个新的
ByteArrayInputStream
,您可以访问该新的
ByteArrayInputStream


你不必关闭这条小溪。
RestTemplate
会为您解决这个问题。(如果您尝试使用另一种由
ResourceHttpMessageConverter
支持的类型,这是很不幸的,因为它包装了底层响应的
InputStream
,但在它暴露于您的客户端代码之前就被关闭了。)

作为一种变体,您可以将响应作为字节使用,而不是转换为流

byte data[] = restTemplate.execute(link, HttpMethod.GET, null, new BinaryFileExtractor());
return new ByteArrayInputStream(data);
提取器是

public class BinaryFileExtractor implements ResponseExtractor<byte[]> {

  @Override
  public byte[] extractData(ClientHttpResponse response) throws IOException {
    return ByteStreams.toByteArray(response.getBody());
  }
}
公共类二进制文件提取器实现ResponseExtractor{
@凌驾
公共字节[]提取数据(ClientHttpResponse响应)引发IOException{
返回ByteStreams.toByteArray(response.getBody());
}
}

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

总之,通过将
Resource.class
指定为
restemplate
调用的响应类型,您实际上可以通过
restemplate
直接获得
InputStream

下面是使用
restemplate
exchange(..)
方法之一的示例:

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
导入org.springframework.web.client.restemplate;
导入org.springframework.http.HttpMethod;
导入org.springframework.core.io.Resource;
ResponseEntity ResponseEntity=restemplate.exchange(someUrlString,HttpMethod.GET,someHttpEntity,Resource.class);
输入流响应输入流;
试一试{
responseInputStream=responseEntity.getBody().getInputStream();
}
捕获(IOE异常){
抛出新的运行时异常(e);
}
//使用responseInputStream

前面的答案没有错,但没有达到我想看到的深度。在某些情况下,处理低级
InputStream
不仅是可取的,而且是必要的,最常见的例子是将大型文件从源(某些web服务器)流式传输到目标(数据库)。如果您尝试使用
ByteArrayInputStream
,您会收到
OutOfMemoryError
,这并不奇怪。是的,您可以滚动自己的HTTP客户端代码,但是您必须处理错误的响应代码、响应转换器等。如果您已经在使用Spring,那么使用
RestTemplate
是一个自然的选择

在撰写本文时,
springweb:5.0.2.RELEASE
有一个
resourcehttmpessageconverter
,它有一个
布尔支持restreaming
,如果设置了,响应类型为
InputStreamResource
,则返回
InputStreamResource
;否则,它将返回一个
ByteArrayResource
。显然,你不是唯一一个要求流媒体支持的人

但是,有一个问题:
restemplate
HttpMessageConverter
运行后不久关闭响应。因此,即使您请求了
InputStreamResource
,并得到了它,也没有什么好处,因为响应流已经关闭。我认为这是他们忽视的一个设计缺陷;它应该取决于响应类型。因此,不幸的是,对于阅读,你必须充分利用回应;如果使用
restemplate
,则无法传递它

不过,写作没有问题。如果您想要流式传输
InputStream
ResourceHttpMessageConverter
将为您提供此功能。在后台,它使用
org.springframework.util.StreamUtils
InputStream
OutputStream
一次写入4096字节

一些
HttpMessageConverter
支持所有媒体类型,因此根据您的要求,您可能需要从
restemplate
中删除默认类型,并设置所需类型,同时注意它们的相对顺序

最后但并非最不重要的一点是,
clienthtprequestfactory
的实现有一个
boolean-bufferRequestBody
,如果您正在上载一个大流,则可以并且应该将其设置为
false
。否则,您知道,
OutOfMemoryError
。在撰写本文时,
SimpleClientHttpRequestFactory
(JDK客户端)和
HttpComponents客户端HttpRequestFactory
(Apache HTTP客户端)支持此功能,但不支持
OkHttp3ClientHttpRequestFactory
。再次强调,设计监督

编辑: 已存档的票据。

感谢Ab
[
    { "property1": "value1", "property2": "value2", "property3": "value3" },
    { "property1": "value1", "property2": "value2", "property3": "value3" },
    ... 1446481 objects => a file of 104 MB => take quite long to download...
]
@lombok.Data
public class Testing {
    String property1;
    String property2;
    String property3;
}
import com.fasterxml.jackson.core.JsonParser;
import java.io.IOException;
@FunctionalInterface
public interface JsonStreamer<R> {
    /**
     * Parse the given JSON stream, process it, and optionally return an object.<br>
     * The returned object can represent a downsized parsed version of the stream, or the result of a map/reduce processing, or null...
     *
     * @param jsonParser the parser to use while streaming JSON for processing
     * @return the optional result of the process (can be {@link Void} if processing returns nothing)
     * @throws IOException on streaming problem (you are also strongly encouraged to throw HttpMessageNotReadableException on parsing error)
     */
    R stream(JsonParser jsonParser) throws IOException;
}
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

@AllArgsConstructor
public class StreamingHttpMessageConverter<R> implements HttpMessageConverter<R> {

    private final JsonFactory factory;
    private final JsonStreamer<R> jsonStreamer;

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return MediaType.APPLICATION_JSON.isCompatibleWith(mediaType);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return false; // We only support reading from an InputStream
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return Collections.singletonList(MediaType.APPLICATION_JSON);
    }

    @Override
    public R read(Class<? extends R> clazz, HttpInputMessage inputMessage) throws IOException {
        try (InputStream inputStream = inputMessage.getBody();
             JsonParser parser = factory.createParser(inputStream)) {
            return jsonStreamer.stream(parser);
        }
    }

    @Override
    public void write(R result, MediaType contentType, HttpOutputMessage outputMessage) {
        throw new UnsupportedOperationException();
    }

}
// You should @Autowire these:
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper objectMapper = new ObjectMapper();
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();

// If detectRequestFactory true (default): HttpComponentsClientHttpRequestFactory will be used and it will consume the entire HTTP response, even if we close the stream early
// If detectRequestFactory false: SimpleClientHttpRequestFactory will be used and it will close the connection as soon as we ask it to

RestTemplate restTemplate = restTemplateBuilder.detectRequestFactory(false).messageConverters(
    new StreamingHttpMessageConverter<>(jsonFactory, jsonParser -> {

        // While you use a low-level JsonParser to not load everything in memory at once,
        // you can still profit from smaller object mapping with the ObjectMapper
        if (!jsonParser.isClosed() && jsonParser.nextToken() == JsonToken.START_ARRAY) {
            if (!jsonParser.isClosed() && jsonParser.nextToken() == JsonToken.START_OBJECT) {
                return objectMapper.readValue(jsonParser, Testing.class);
            }
        }
        return null;

    })
).build();

final Testing firstTesting = restTemplate.getForObject("http://example.org/testings.json", Testing.class);
log.debug("First testing object: {}", firstTesting);
        RestTemplate restTemplate = new RestTemplateBuilder().basicAuthentication("user", "their_password" ).build();

        int responseSize = restTemplate.execute(uri,
            HttpMethod.POST,
            (ClientHttpRequest requestCallback) -> {
                requestCallback.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                requestCallback.getBody().write(body.getBytes());
            },
            responseExtractor -> {
                FileOutputStream fos  = new FileOutputStream(new File("out.json"));
                return StreamUtils.copy(responseExtractor.getBody(), fos);
            }
    )
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);
}
Consumer<InputStream> doWhileDownloading = inputStream -> {
                //Use inputStream for your business logic...
};

consumerInputStreamWithoutBuffering("https://localhost.com/download", doWhileDownloading);
public InputStream getInputStreamFromResponse(String url) throws IOException {

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

    return restTemplate.execute(url, HttpMethod.GET, null, responseExtractor);
}