java inputstream resttemplate

java - Obteniendo InputStream con RestTemplate



(5)

Como variante puede consumir la respuesta como bytes y luego convertir a secuencia

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

Extractor es

public class BinaryFileExtractor implements ResponseExtractor<byte[]> { @Override public byte[] extractData(ClientHttpResponse response) throws IOException { return ByteStreams.toByteArray(response.getBody()); } }

Estoy usando la clase de URL para leer un InputStream de ella. ¿Hay alguna manera de que pueda usar RestTemplate para esto?

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

¿Cómo puedo obtener InputStream con RestTemplate lugar de usar la URL ?


Gracias a la respuesta de Abhijit Sarkar por liderar el camino.

Necesitaba descargar un flujo JSON pesado y dividirlo en pequeños datos manejables y manejables. El JSON está compuesto de objetos que tienen grandes propiedades: tales grandes propiedades pueden ser serializadas en un archivo y, por lo tanto, eliminadas del objeto JSON no agrupado.

Otro caso de uso es descargar un objeto JSON de flujo por objeto, procesarlo como un algoritmo de mapa / reducción y producir una salida única sin tener que cargar todo el flujo en la memoria.

Otro caso de uso es leer un archivo JSON grande y elegir solo unos pocos objetos en función de una condición, mientras que no es demasiado estricto para los objetos Java antiguos.

Aquí hay un ejemplo: nos gustaría transmitir un archivo JSON muy grande que es una matriz, y nos gustaría recuperar solo el primer objeto de la matriz.

Dado este gran archivo en un servidor, disponible en 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... ]

Cada fila de esta matriz JSON se puede analizar como este objeto:

@lombok.Data public class Testing { String property1; String property2; String property3; }

Necesita esta clase para hacer reutilizable el código de análisis:

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; }

Y esta clase para analizar:

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(); } }

Luego, aquí está el código que se usará para transmitir la respuesta HTTP, analizar la matriz JSON y devolver solo el primer objeto no superpuesto:

// 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);


Las respuestas anteriores no son incorrectas, pero no entran en la profundidad que me gusta ver. Hay casos en los que tratar con InputStream bajo nivel no solo es deseable, sino necesario, ya que el ejemplo más común es transmitir un archivo grande desde el origen (algunos servidores web) hasta el destino (una base de datos). Si intentas usar un ByteArrayInputStream , serás recibido, como es OutOfMemoryError , con OutOfMemoryError . Sí, puede pasar su propio código de cliente HTTP, pero tendrá que lidiar con códigos de respuesta erróneos, convertidores de respuesta, etc. Si ya está usando Spring, mirar a RestTemplate es una opción natural.

En el momento de escribir este artículo, spring-web:5.0.2.RELEASE tiene un ResourceHttpMessageConverter que tiene un boolean supportsReadStreaming , que se configura, y que el tipo de respuesta es InputStreamResource , devuelve InputStreamResource ; De lo contrario, devuelve un ByteArrayResource . Claramente, no eres el único que pidió soporte de transmisión.

Sin embargo, hay un problema: RestTemplate cierra la respuesta poco después de que se ejecute HttpMessageConverter . Por lo tanto, incluso si usted solicitó InputStreamResource y lo obtuvo, no es bueno, porque la secuencia de respuesta se ha cerrado. Creo que este es un defecto de diseño que pasaron por alto; debería haber sido dependiente del tipo de respuesta. Desafortunadamente, para leer, debes consumir la respuesta completamente; no puedes pasarlo si usas RestTemplate .

Sin embargo, escribir no es un problema. Si desea transmitir un InputStream , ResourceHttpMessageConverter lo hará por usted. Bajo el capó, usa org.springframework.util.StreamUtils para escribir 4096 bytes a la vez desde InputStream hasta OutputStream .

Algunos de los HttpMessageConverter compatibles con todos los tipos de medios, por lo que, dependiendo de sus necesidades, es posible que deba eliminar las predeterminadas de RestTemplate y configurar las que necesita, teniendo en cuenta su orden relativo.

Por último, pero no menos importante, las implementaciones de ClientHttpRequestFactory tienen un boolean bufferRequestBody que puede, y debe, establecer en false si está cargando un flujo grande. De lo contrario, ya sabes, OutOfMemoryError . Al momento de escribir este artículo, SimpleClientHttpRequestFactory (cliente JDK) y HttpComponentsClientHttpRequestFactory (cliente HTTP Apache) admiten esta función, pero no OkHttp3ClientHttpRequestFactory . Una vez más, la supervisión del diseño.

Edición : Billete archivado SPR-16885 .


No deberias RestTemplate está destinado a encapsular el procesamiento del contenido de respuesta (y solicitud).

En su lugar, puede registrar objetos HttpMessageConverter apropiados. Esos tendrán acceso al InputStream la respuesta, a través de un objeto HttpInputMessage .

Como sugiere Abdull , Spring viene con una implementación HttpMessageConverter para Resource que a su vez envuelve un InputStream , ResourceHttpMessageConverter . No es compatible con todos los tipos de Resource , pero como debería estar programando para interfaces de todos modos, debería usar el Resource superinterfaz.

La implementación actual (4.3.5) devolverá un ByteArrayResource con el contenido del flujo de respuesta copiado en un nuevo ByteArrayInputStream que puede acceder.

No tienes que cerrar la corriente. El RestTemplate se encarga de eso por ti. (Esto es desafortunado si intenta usar un InputStreamResource , otro tipo compatible con ResourceHttpMessageConverter , ya que envuelve el InputStream la respuesta subyacente pero se cierra antes de que pueda ser expuesto a su código de cliente).


Spring tiene un org.springframework.http.converter.ResourceHttpMessageConverter . Convierte la clase org.springframework.core.io.Resource de Spring. Esa clase de Resource encapsula un InputStream , que puede obtener a través de someResource.getInputStream() .

Poniendo todo esto en conjunto, puede obtener un InputStream través de RestTemplate fuera de la caja especificando Resource.class como el RestTemplate de respuesta de su invocación RestTemplate .

Aquí hay un ejemplo usando uno de los RestTemplate de exchange(..) de RestTemplate :

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