web-dev-qa-db-fra.com

block () / blockFirst () / blockLast () bloquent l'erreur lors de l'appel de bodyToMono AFTER exchange ()

J'essaie d'utiliser Webflux pour diffuser un fichier généré vers un autre emplacement, cependant, si la génération du fichier s'est heurtée à une erreur, l'api renvoie le succès, mais avec un DTO détaillant les erreurs lors de la génération du fichier au lieu du fichier lui-même. Il s'agit d'une API très ancienne et mal conçue, veuillez donc excuser l'utilisation de la publication et de la conception de l'API.

La réponse de l'appel api (exchange ()) est un ClientResponse. À partir d'ici, je peux soit convertir en ByteArrayResource en utilisant bodyToMono qui peut être diffusé dans un fichier, ou, s'il y a une erreur dans la création du fichier, alors je peux convertir en DTO en utilisant également bodyToMono. Cependant, je ne peux pas sembler le faire ou selon le contenu de l'en-tête de ClientResponse.

Au moment de l'exécution, j'obtiens une exception IllegalStateException causée par

block ()/blockFirst ()/blockLast () bloquent, ce qui n'est pas pris en charge dans thread réacteur-http-client-epoll-12

Je pense que mon problème est que je ne peux pas appeler block () deux fois dans la même chaîne de fonctions.

Mon extrait de code ressemble à ceci:

webClient.post()
        .uri(uriBuilder -> uriBuilder.path("/file/")
                                      .queryParams(params).build())
        .exchange()
        .doOnSuccess(cr -> {
                if (MediaType.APPLICATION_JSON_UTF8.equals(cr.headers().contentType().get())) {
                    NoPayloadResponseDto dto = cr.bodyToMono(NoPayloadResponseDto.class).block();
                    createErrorFile(dto);
                }
                else {
                    ByteArrayResource bAr = cr.bodyToMono(ByteArrayResource.class).block();
                    createSpreadsheet(bAr);
                }
            }
        )
        .block();

Fondamentalement, je veux traiter différemment la ClientResponse en fonction du MediaType qui est défini dans l'en-tête.

Est-ce possible?

7
DaithiG

Tout d'abord, quelques éléments qui vous aideront à comprendre l'extrait de code permettant de résoudre ce cas d'utilisation.

  1. Vous ne devez jamais appeler une méthode de blocage dans une méthode qui renvoie un type réactif; vous bloquerez l'un des rares threads de votre application et c'est très mauvais pour l'application
  2. Quoi qu'il en soit à partir de Reactor 3.2, le blocage dans un pipeline réactif génère une erreur
  3. Appeler subscribe, comme suggéré dans les commentaires, n'est pas non plus une bonne idée. C'est plus ou moins comme démarrer ce travail en tant que tâche dans un thread séparé. Vous recevrez un rappel une fois terminé (les méthodes subscribe peuvent recevoir des lambdas), mais vous découplez en fait votre pipeline actuel avec cette tâche. Dans ce cas, la réponse HTTP du client pourrait être fermée et les ressources nettoyées avant de pouvoir lire le corps de la réponse complète pour l'écrire dans un fichier
  4. Si vous ne voulez pas mettre la réponse entière en mémoire tampon, Spring fournit DataBuffer (pensez aux instances ByteBuffer qui peuvent être regroupées).
  5. Vous pouvez appeler block si la méthode que vous implémentez est elle-même bloquante (en retournant void par exemple), par exemple dans un cas de test.

Voici un extrait de code que vous pouvez utiliser pour ce faire:

Mono<Void> fileWritten = WebClient.create().post()
        .uri(uriBuilder -> uriBuilder.path("/file/").build())
        .exchange()
        .flatMap(response -> {
            if (MediaType.APPLICATION_JSON_UTF8.equals(response.headers().contentType().get())) {
                Mono<NoPayloadResponseDto> dto = response.bodyToMono(NoPayloadResponseDto.class);
                return createErrorFile(dto);
            }
            else {
                Flux<DataBuffer> body = response.bodyToFlux(DataBuffer.class);
                return createSpreadsheet(body);
            }
        });
// Once you get that Mono, you should give plug it into an existing
// reactive pipeline, or call block on it, depending on the situation

Comme vous pouvez le voir, nous ne bloquons nulle part et les méthodes traitant des E/S retournent Mono<Void>, Qui est l'équivalent réactif d'une fonction de rappel done(error) qui signale quand les choses sont terminées et si une erreur s'est produite.

Comme je ne suis pas sûr de ce que la méthode createErrorFile devrait faire, j'ai fourni un exemple pour createSpreadsheet qui écrit simplement les octets du corps dans un fichier. Notez que comme les tampons de données peuvent être recyclés/regroupés, nous devons les libérer une fois que nous avons terminé.

private Mono<Void> createSpreadsheet(Flux<DataBuffer> body) {
    try {
        Path file = //...
        WritableByteChannel channel = Files.newByteChannel(file, StandardOpenOption.WRITE);
        return DataBufferUtils.write(body, channel).map(DataBufferUtils::release).then();
    } catch (IOException exc) {
        return Mono.error(exc);
    }
}

Avec cette implémentation, votre application conservera quelques instances de DataBuffer en mémoire à un moment donné (les opérateurs réactifs prélectent des valeurs pour des raisons de performances) et écrira des octets au fur et à mesure de leur réactivité.

5
Brian Clozel

Dans mon cas, je devais juste remplacer exchange et block par retrieve.

Celui-ci a provoqué l'erreur:

Mono<Boolean> booleanMono = webClient.get()
                  .exchange().block().bodyToMono(Boolean.class);

Remplacer ce qui précède par la ligne suivante a résolu mon problème:

Mono<Boolean> booleanMono = webClient.get()
                 .retrieve().bodyToMono(Boolean.class);
0
KernelMode
RestResultMessage message= createWebClient()
                .get()
                .uri(uri)
                .exchange()
                .map(clientResponse -> {
                    //delegation
                    ClientResponseWrapper wrapper = new 
                                 ClientResponseWrapper(clientResponse);
                    return Mono.just(wrapper);
                })
                .block() //wait until request is not done
                .map(result -> {  
                    //convert to any data
                    if (!result.statusCode().isError()){
                       //extract the result from request
                        return create(RestResultMessage.Result.success, result.bodyToMono(String.class).block());}
                    } else {
                        return create(RestResultMessage.Result.error, result.statusCode().name());
                    }
                })
                .block();
0
Nurlan Rysbaev