URLクラスを使用して、そこからInputStreamを読み取ります。このためにRestTemplateを使用する方法はありますか?
InputStream input = new URL(url).openStream();
JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName()));
InputStream
を使用する代わりにRestTemplate
でURL
を取得するにはどうすればよいですか?
InputStream
を直接取得しないでください。 RestTemplate
は、応答(および要求)コンテンツの処理をカプセル化するためのものです。その強みは、すべてのIOを処理し、すぐに使用できるJavaオブジェクトを渡すことです。
適切な HttpMessageConverter
オブジェクトを登録する必要があります。これらは、 InputStream
オブジェクトを介して、応答のHttpInputMessage
にアクセスできます。
Abdullが示唆するとおり 、SpringにはHttpMessageConverter
のResource
実装が付属しており、それ自体がInputStream
をラップしています ResourceHttpMessageConverter
。すべてのResource
型をサポートしているわけではありませんが、とにかくインターフェイスにプログラミングする必要があるため、スーパーインターフェイスResource
を使用するだけです。
現在の実装(4.3.5)は、応答ストリームの内容がアクセス可能な新しいByteArrayResource
にコピーされた ByteArrayInputStream
を返します。
ストリームを閉じる必要はありません。 RestTemplate
がそれを処理します。 (これは、InputStreamResource
でサポートされている別のタイプである ResourceHttpMessageConverter
を使用しようとすると不幸です。なぜなら、基になる応答のInputStream
をラップするが、クライアントコードに公開される前に閉じられるためです。)
前の答えは間違っていませんが、私が見たいと思う深みには入りません。低レベルInputStream
を扱うことが望ましいだけでなく、必要な場合があります。最も一般的な例は、ソース(一部のWebサーバー)から宛先(データベース)に大きなファイルをストリーミングすることです。 ByteArrayInputStream
を使用しようとすると、それほど驚くことではありませんが、OutOfMemoryError
で迎えられます。はい、独自のHTTPクライアントコードをロールできますが、誤った応答コード、応答コンバーターなどに対処する必要があります。既にSpringを使用している場合は、RestTemplate
を探すのが自然な選択です。
この記事の執筆時点では、spring-web:5.0.2.RELEASE
にはResourceHttpMessageConverter
があり、boolean supportsReadStreaming
は、設定されていて、応答タイプがInputStreamResource
の場合、InputStreamResource
を返します。それ以外の場合は、ByteArrayResource
を返します。明らかに、ストリーミングサポートを求めたのはあなただけではありません。
ただし、問題があります。RestTemplate
は、HttpMessageConverter
の実行後すぐに応答を閉じます。したがって、InputStreamResource
を要求して取得したとしても、応答ストリームが閉じられているため、それは役に立ちません。これは彼らが見落としていた設計上の欠陥だと思います。応答タイプに依存しているはずです。残念ながら、読むには応答を完全に消費する必要があります。 RestTemplate
を使用している場合、渡すことはできません。
しかし、書くことは問題ありません。 InputStream
をストリーミングしたい場合、ResourceHttpMessageConverter
がそれを行います。内部では、org.springframework.util.StreamUtils
InputStream
からOutputStream
に一度に4096バイトを書き込みます。
一部のHttpMessageConverter
はすべてのメディアタイプをサポートしているため、要件に応じて、RestTemplate
からデフォルトのメディアを削除し、相対的な順序に注意して必要なメディアを設定する必要があります。
最後になりますが、ClientHttpRequestFactory
の実装にはboolean bufferRequestBody
大きいストリームをアップロードする場合、false
に設定できます。また、設定する必要があります。それ以外の場合は、OutOfMemoryError
です。この記事の執筆時点では、SimpleClientHttpRequestFactory
(JDKクライアント)およびHttpComponentsClientHttpRequestFactory
(Apache HTTPクライアント)はこの機能をサポートしていますが、OkHttp3ClientHttpRequestFactory
。繰り返しますが、設計の監督。
編集:チケットを提出 SPR-16885 .
Springには_org.springframework.http.converter.ResourceHttpMessageConverter
_があります。 Springの_org.springframework.core.io.Resource
_クラスを変換します。そのResource
クラスはInputStream
をカプセル化し、someResource.getInputStream()
を介して取得できます。
これをすべてまとめると、InputStream
呼び出しの応答タイプとして_Resource.class
_を指定することで、すぐにRestTemplate
経由でRestTemplate
を取得できます。
RestTemplate
のexchange(..)
メソッドの1つを使用した例を次に示します。
_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
_
道をリードしてくれたAbhijit Sarkarの回答に感謝します。
重いJSONストリームをダウンロードし、それを小さなストリーミング可能な管理可能なデータに分割する必要がありました。 JSONは大きなプロパティを持つオブジェクトで構成されます。このような大きなプロパティはファイルにシリアル化できるため、非整列化されたJSONオブジェクトから削除できます。
別の使用例は、JSONストリームオブジェクトをオブジェクトごとにダウンロードし、map/reduceアルゴリズムのように処理し、メモリにストリーム全体をロードすることなく単一の出力を生成することです。
さらに別の使用例は、大きなJSONファイルを読み取り、条件に基づいて少数のオブジェクトのみを選択する一方で、Plain Old Java Objects。
次に例を示します。配列である非常に巨大なJSONファイルをストリーミングし、配列の最初のオブジェクトのみを取得したいと考えています。
サーバー上のこの大きなファイルを考えると、 http://example.org/testings.json で利用可能です:
[
{ "property1": "value1", "property2": "value2", "property3": "value3" },
{ "property1": "value1", "property2": "value2", "property3": "value3" },
... 1446481 objects => a file of 104 MB => take quite long to download...
]
このJSON配列の各行は、このオブジェクトとして解析できます。
@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();
}
}
次に、HTTP応答のストリーミング、JSON配列の解析、および最初の非整列化オブジェクトのみを返すために使用するコードを次に示します。
// 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);
独自の応答抽出プログラムを渡すことができます。次に、ストリーミング形式でjsonをディスクに書き出す例を示します-
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);
}
)
バリアントとして、応答をバイトとして消費し、ストリームに変換することができます
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());
}
}