Получение InputStream с помощью RestTemplate
Я использую класс URL для чтения InputStream из него. Есть ли способ использовать RestTemplate для этого?
InputStream input = new URL(url).openStream();
JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName()));
как я могу получить InputStream
С RestTemplate
вместо URL
?
5 ответов
вы не должны. RestTemplate
предназначен для инкапсуляции обработки содержимого ответа (и запроса).
вместо этого вы можете зарегистрировать соответствующий HttpMessageConverter
объекты. У них будет доступ к ответу InputStream
, через HttpInputMessage
Весна org.springframework.http.converter.ResourceHttpMessageConverter
. Он преобразует весны org.springframework.core.io.Resource
класса.
Это Resource
класс инкапсулирует InputStream
, который вы можете получить через someResource.getInputStream()
.
собрав все это вместе, вы действительно можете получить InputStream
via RestTemplate
вне---коробку с указанием Resource.class
в своем RestTemplate
тип ответа вызова.
вот пример использования одного из RestTemplate
' s 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
предыдущие ответы не ошибочны, но они не идут в глубину, которую мне нравится видеть. Бывают случаи, когда дело касается низкого уровня InputStream
не только желательно, но и необходимо, наиболее распространенным примером является потоковая передача большого файла из источника (некоторого веб-сервера) в пункт назначения (базу данных). Если вы попытаетесь использовать 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
для записи 4096 байт за раз из InputStream
к OutputStream
.
часть HttpMessageConverter
поддержка всех типов носителей, поэтому в зависимости от вашего требования вам может потребоваться удалить стандартные из RestTemplate
, и установите те, которые вам нужны, помня об их относительном порядке.
последнее, но не менее, реализаций ClientHttpRequestFactory
есть boolean bufferRequestBody
что вы можете, и должны, установленной false
если вы загружаете большой поток. Иначе, знаете ли,OutOfMemoryError
. На момент написания этой статьи SimpleClientHttpRequestFactory
(клиент JDK) и HttpComponentsClientHttpRequestFactory
(Apache HTTP client) поддерживает эту функцию, но не OkHttp3ClientHttpRequestFactory
. Опять же, надзор за дизайном.
редактировать: Подал билет SPR-16885.
в качестве варианта вы можете использовать ответ как байты, а затем конвертировать в stream
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());
}
}
благодаря ответу Абхиджита Саркара за руководство.
мне нужно было загрузить тяжелый поток JSON и разбить его на небольшие потоковые управляемые фрагменты данных. JSON-файл, который состоит из объектов, которые имеют большие свойства: такие большие свойства могут быть сериализованы в файл, и таким образом удалены из неупорядоченными объект JSON.
другой вариант использования-загрузить объект потока JSON по объекту, обработать его как алгоритм map/reduce и произвести один выход без необходимости загружать весь поток в память.
еще один вариант использования-прочитать большой JSON-файл и выбрать только несколько объектов на основе условия, а не просто старые объекты Java.
вот пример: мы хотели бы передать очень огромный файл 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);