Получение 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);