J'utilise WebClient
et custom BodyExtractor
class pour mon application spring-boot
WebClient webLCient = WebClient.create();
webClient.get()
.uri(url, params)
.accept(MediaType.APPLICATION.XML)
.exchange()
.flatMap(response -> {
return response.body(new BodyExtractor());
})
BodyExtractor.Java
@Override
public Mono<T> extract(ClientHttpResponse response, BodyExtractor.Context context) {
Flux<DataBuffer> body = response.getBody();
body.map(dataBuffer -> {
try {
JaxBContext jc = JaxBContext.newInstance(SomeClass.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (T) unmarshaller.unmarshal(dataBuffer.asInputStream())
} catch(Exception e){
return null;
}
}).next();
}
Le code ci-dessus fonctionne avec une petite charge utile mais pas avec une charge importante. Je pense que c'est parce que je ne lis qu'une valeur de flux avec next
et je ne suis pas sûr de savoir comment combiner et lire toutes les dataBuffer
.
Je suis nouveau dans le réacteur, donc je ne connais pas beaucoup d’astuces avec Flux/Mono.
J'ai pu le faire fonctionner en utilisant Flux#collect
et SequenceInputStream
@Override
public Mono<T> extract(ClientHttpResponse response, BodyExtractor.Context context) {
Flux<DataBuffer> body = response.getBody();
return body.collect(InputStreamCollector::new, (t, dataBuffer)-> t.collectInputStream(dataBuffer.asInputStream))
.map(inputStream -> {
try {
JaxBContext jc = JaxBContext.newInstance(SomeClass.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (T) unmarshaller.unmarshal(inputStream);
} catch(Exception e){
return null;
}
}).next();
}
InputStreamCollector.Java
public class InputStreamCollector {
private InputStream is;
public void collectInputStream(InputStream is) {
if (this.is == null) this.is = is;
this.is = new SequenceInputStream(this.is, is);
}
public InputStream getInputStream() {
return this.is;
}
}
Reconstruire la InputStream
annule l'objectif d'utiliser WebClient
car rien ne sera émis jusqu'à ce que l'opération collect
soit terminée. Pour un grand flux, cela peut être très long. Le modèle réactif ne traite pas d'octets individuels, mais de blocs d'octets (comme Spring DataBuffer
). Voir ma réponse ici pour une solution plus élégante: https://stackoverflow.com/a/48054615/839733
Une version légèrement modifiée de la réponse de Bk Santiago utilise reduce()
au lieu de collect()
. Très similaire, mais ne nécessite pas de classe supplémentaire:
Java:
body.reduce(new InputStream() {
public int read() { return -1; }
}, (s: InputStream, d: DataBuffer) -> new SequenceInputStream(s, d.asInputStream())
).flatMap(inputStream -> /* do something with single InputStream */
Ou Kotlin:
body.reduce(object : InputStream() {
override fun read() = -1
}) { s: InputStream, d -> SequenceInputStream(s, d.asInputStream()) }
.flatMap { inputStream -> /* do something with single InputStream */ }
L'avantage de cette approche par rapport à l'utilisation de collect()
est simplement qu'il n'est pas nécessaire d'avoir une classe différente pour rassembler les choses.
J'ai créé un nouveau InputStream()
vide, mais si cette syntaxe est source de confusion, vous pouvez également le remplacer par ByteArrayInputStream("".toByteArray())
pour créer une ByteArrayInputStream
vide comme valeur initiale.