J'utilise une classe d'URL pour lire un InputStream. Est-il possible d'utiliser RestTemplate pour cela?
InputStream input = new URL(url).openStream();
JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName()));
Comment puis-je obtenir InputStream
avec RestTemplate
au lieu d'utiliser URL
?
Vous ne devriez pas obtenir le InputStream
directement. RestTemplate
est destiné à encapsuler le traitement du contenu de la réponse (et de la requête). Sa force est de gérer tous les IO et de vous remettre un objet Java prêt à l'emploi.
Vous devrez enregistrer les objets appropriés HttpMessageConverter
. Ceux-ci auront accès à la InputStream
de la réponse, via un HttpInputMessage
object.
Comme Abdull le suggère , Spring est livré avec une implémentation HttpMessageConverter
pour Resource
qui enveloppe elle-même une InputStream
, ResourceHttpMessageConverter
. Il ne prend pas en charge tous les types Resource
, mais comme vous devriez quand même programmer des interfaces, vous devriez simplement utiliser la superinterface Resource
.
L’implémentation actuelle (4.3.5) renverra une ByteArrayResource
avec le contenu du flux de réponses copié dans une nouvelle ByteArrayInputStream
à laquelle vous pourrez accéder.
Vous n'êtes pas obligé de fermer le flux. La RestTemplate
s'en occupe pour vous. (Cela est regrettable si vous essayez d'utiliser un InputStreamResource
, un autre type pris en charge par ResourceHttpMessageConverter
, car il enveloppe la réponse InputStream
de la réponse sous-jacente mais est fermé avant de pouvoir être exposé à votre code client.)
Les réponses précédentes ne sont pas fausses, mais elles n'entrent pas dans la profondeur que j'aime voir. Il existe des cas où traiter InputStream
de bas niveau est non seulement souhaitable, mais nécessaire, l'exemple le plus courant étant la transmission en continu d'un fichier volumineux de la source (un serveur Web) à la destination (une base de données). Si vous essayez d'utiliser un ByteArrayInputStream
, vous serez, sans surprise, accueilli par OutOfMemoryError
. Oui, vous pouvez utiliser votre propre code de client HTTP, mais vous devrez gérer les codes de réponse, les convertisseurs de réponse, etc. erronés. Si vous utilisez déjà Spring, il est naturel de choisir RestTemplate
.
Au moment de cette écriture, spring-web:5.0.2.RELEASE
a une ResourceHttpMessageConverter
qui a un boolean supportsReadStreaming
, qui si défini, et le type de réponse est InputStreamResource
, retourne InputStreamResource
; sinon, il retourne une ByteArrayResource
. Il est donc clair que vous n'êtes pas le seul à avoir demandé une assistance en streaming.
Cependant, il existe un problème: RestTemplate
ferme la réponse peu de temps après l'exécution de HttpMessageConverter
. Ainsi, même si vous avez demandé InputStreamResource
et l'avez obtenu, ce n'est pas bon, car le flux de réponse a été fermé. Je pense que c'est un défaut de conception qu'ils ont négligé; cela aurait dû dépendre du type de réponse. Donc, malheureusement, pour lire, vous devez consommer pleinement la réponse; vous ne pouvez pas le faire circuler si vous utilisez RestTemplate
.
L'écriture n'est pas un problème cependant. Si vous souhaitez diffuser une InputStream
, ResourceHttpMessageConverter
le fera pour vous. Sous le capot, il utilise org.springframework.util.StreamUtils
pour écrire 4 096 octets à la fois, de InputStream
à OutputStream
.
Certains des HttpMessageConverter
prennent en charge tous les types de supports. Ainsi, selon vos besoins, vous devrez peut-être supprimer ceux par défaut de RestTemplate
et définir ceux dont vous avez besoin, en tenant compte de leur ordre relatif.
Dernier point mais non le moindre, les implémentations de ClientHttpRequestFactory
ont un boolean bufferRequestBody
que vous pouvez et devez définir sur false
si vous téléchargez un flux volumineux. Sinon, vous savez, OutOfMemoryError
. Au moment de la rédaction de ce document, SimpleClientHttpRequestFactory
(client JDK) et HttpComponentsClientHttpRequestFactory
(client HTTP Apache) prennent en charge cette fonctionnalité, mais pas OkHttp3ClientHttpRequestFactory
. Encore une fois, la supervision de la conception.
Éditer: Ticket archivé SPR-16885 .
Spring a un org.springframework.http.converter.ResourceHttpMessageConverter
. Il convertit la classe org.springframework.core.io.Resource
..__ de Spring. Cette classe Resource
encapsule une InputStream
, que vous pouvez obtenir via someResource.getInputStream()
.
En réunissant tous ces éléments, vous pouvez obtenir une variable InputStream
via RestTemplate
prête à l'emploi en spécifiant Resource.class
comme type de réponse de votre invocation RestTemplate
.
Voici un exemple utilisant l’une des méthodes 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
Merci à Abhijit Sarkar pour sa réponse.
J'avais besoin de télécharger un flux JSON lourd et de le diviser en petits morceaux de données gérables en streaming . Le JSON est composé d'objets qui ont de grandes propriétés: de telles grandes propriétés peuvent être sérialisées dans un fichier, et ainsi supprimées du fichier JSON incontrôlé. objet.
Un autre cas d'utilisation consiste à télécharger un flux JSON objet par objet, à le traiter comme un algorithme map/réduire et à produire une sortie unique sans avoir à charger tout le flux en mémoire.
Encore un autre cas d’utilisation consiste à lire un gros fichier JSON et à ne sélectionner que quelques objets en fonction d’une condition, tout en respectant l’objet Plain Old Java Objects.
Voici un exemple: nous aimerions diffuser un très gros fichier JSON qui est un tableau et nous ne récupérerons que le premier objet du tableau.
Étant donné ce gros fichier sur un serveur, disponible à l’adresse 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...
]
Chaque ligne de ce tableau JSON peut être analysée comme cet objet:
@lombok.Data
public class Testing {
String property1;
String property2;
String property3;
}
Vous avez besoin de cette classe pour que le code d’analyse soit réutilisable:
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;
}
Et cette classe à analyser:
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();
}
}
Ensuite, voici le code à utiliser pour diffuser en continu la réponse HTTP, analyser le tableau JSON et renvoyer uniquement le premier objet unmarshalled:
// 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);
Vous pouvez passer votre propre extracteur de réponse. Voici un exemple où j’écris json sur disque en streaming -
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);
}
)
En variante, vous pouvez utiliser la réponse sous forme d'octets et ensuite convertir en flux
byte data[] = restTemplate.execute(link, HttpMethod.GET, null, new BinaryFileExtractor());
return new ByteArrayInputStream(data);
L'extracteur est
public class BinaryFileExtractor implements ResponseExtractor<byte[]> {
@Override
public byte[] extractData(ClientHttpResponse response) throws IOException {
return ByteStreams.toByteArray(response.getBody());
}
}