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?
Tout d'abord, quelques éléments qui vous aideront à comprendre l'extrait de code permettant de résoudre ce cas d'utilisation.
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 fichierDataBuffer
(pensez aux instances ByteBuffer qui peuvent être regroupées).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é.
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);
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();