How to perform consecutive web requests based on another request in one Mono in Spring WebFlux?
Asked Answered
C

2

5

I want to do the following via Spring WebFlux and a given REST-API:

  1. Retrieve a list of file names (GET /files)
  2. Delete each retrieved file (DELETE /files/local/{file name} for each)

The problem is that I cannot combine both actions to "one" Mono instance. My current implementation is insufficient, because it blocks the Mono instances to execute the api calls immediately insteaf of performing them reactive.

My non reactive implementation:

public Mono cleanUpUploadedFiles() {    
    WebClient webClient = this.getWebClient();

    // get all files / directories
    Mono<FilesOverview> filesOverviewMono = this.getResource("/files", FilesOverview.class);
    FilesOverview filesOverview = filesOverviewMono.block(); // TODO: prevent blocking mono

    // delete file / directory one by one
    for (FileOverview file : filesOverview.getFiles()) {
        ClientResponse clientResponse;

        clientResponse = webClient
                .delete()
                .uri(String.format("/files/local/%s", file.getName()))
                .exchange()
                .block(); // TODO: prevent blocking mono
        if (clientResponse == null) {
            return Mono.error(new MyException(String.format("could not execute rest call to delete uploaded files with uuid %s", file.getName())));
        }

        HttpStatus clientResponseStatusCode = clientResponse.statusCode();
        if (clientResponseStatusCode.isError()) {
            return Mono.error(new MyException(String.format("cannot delete uploaded files with uuid %s", file.getName())));
        }
    }

    return Mono.empty(); // TODO: return Mono instance performing everything reactive without blocking
}

How to perform the consecutive web requests in one Mono instance reactive?

Crossbar answered 17/3, 2019 at 11:17 Comment(0)
A
9

You should chain all of your operations in order to create a reactive stream. Usually you take the output of one operation and use it as input for another operation. Project Reactor provides a lot of map and flatMap operators to accomplish this.

In your example you should retrieve the list of files and then map each element to the delete operation like this:

public Mono<Void> cleanUpUploadedFiles() {
    return getResource("/files", FilesOverview.class) // Retrieve the file list
            .flatMapIterable(FilesOverview::getFiles) // Create a Flux from the file list
            .map(FileOverview::getName) // Map the file overview to the file name
            .flatMap(this::deleteFile) // Delete the file
            .then(); // Just return a Mono<Void>
}

private Mono<Void> deleteFile(String fileName) {
    return getWebClient()
            .delete()
            .uri("/files/local/{fileName}", fileName)
            .exchange() // Perform the delete operation
            .onErrorMap(e -> new MyException(String.format("could not execute rest call to delete uploaded files with uuid %s", fileName))) // Handle errors
            .map(ClientResponse::statusCode) // Map the response to the status code
            .flatMap(statusCode -> {
                // If the operation was not successful signal an error
                if (statusCode.isError()) {
                    return Mono.error(new MyException(String.format("cannot delete uploaded files with uuid %s", fileName)));
                }

                // Otherwise return a Mono<Void>
                return Mono.empty();
            });
}
Abuzz answered 18/3, 2019 at 8:47 Comment(1)
Thank you for the great sample :)Crossbar
E
0

It should be something like below:

public Mono cleanUpUploadedFiles() {    
WebClient webClient = this.getWebClient();

// get all files / directories
Mono<FilesOverview> filesOverviewMono = this.getResource("/files", FilesOverview.class);
return filesOverviewMono.flatMap(file ->{
        return webClient
                .delete()
                .uri(String.format("/files/local/%s", file.getName()))
                .exchange()
                .flatMap()//normal scenario
                .onErrorResume()//error
                .switchIfEmpty();//empty body
                //each of these will give you a Mono, so you would need to 
                //transform all of them to finally give a Mono as needed by 
                //the method

    }).flatMap();//transform

}

Essam answered 17/3, 2019 at 11:35 Comment(1)
Thanks for the answer. The first api call returns a list of files as FilesOverview. I miss the deletion of each (!) file in the lambda for filesOverviewMono.flatMap. The sample would match if there would be one file to delete afterwards, I have to delete n files from filesOverview.getFiles().Crossbar

© 2022 - 2024 — McMap. All rights reserved.