web-dev-qa-db-fra.com

Comment lire correctement Flux <DataBuffer> et le convertir en un seul inputStream

J'utilise WebClient et custom BodyExtractorclass 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.

6
Bk Santiago

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;
  }
}
0
Bk Santiago

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

4
Abhijit Sarkar

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.

0
samanime