springspring-bootspring-reactivespring-mono

How to generate a random string and push it out using Mono to be displayed in a browser every X seconds or with random delay through Spring Reactive?


I would like the browser to display the randomly generated string on the browser as soon as the API generates it using Spring Reactive Mono.

Following is my sample program that works, generates random string and displays it on the browser every second but the page does not load any new data and it kind of looks like static data loading.

@RestController
public class LogTailerController {

    @GetMapping(path = "/", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Mono<String> feed() {
        return Mono.just("foo-" + Math.random()).delayElement(Duration.ofSeconds(1));
    }

}

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Solution

  • You may want to look into ConnectableFlux, which is similar to Flux, but is specifically designed to continuously emit elements. You can create a WebClient object, which produces a Mono via its exchange method by default. Then, simply refer to the route you created in your LogRailerController class to call the feed method.

    public void connectFeed() {
    
            ConnectableFlux<String> printEverySecond = WebClient.create("/") // Since your route is "/"
                    .post()
                    .body(...)
                    .exchange() // produces a Mono object
                    .flatMap(response -> response.bodyToMono(String.class)) // transformed into a Mono<String> 
                    .flux() // now a Flux<String>
                    .replay(Duration.of(1, ChronoUnit.SECONDS))
                    .publish(); // now a ConnectableFlux<String>
    
            printEverySecond.subscribe();
            printEverySecond.connect();
    
    }
    

    Instead of using post().getBody()...flatMap(...), you could also just use get(), and call .bodyToMono(String.class) right after .exchange.

    Doing this, you even place your feed() logic in the flatMap. The main issue with this strategy, and when using @RestController too, is that the request will eventually time out, which is kind of tricky get around using RxNetty. With that said, I'd recommend having a separate component class that calls ClientClass.printEverySecond() when it returns after 10 replays, or every 10 seconds, or whichever way you think best. The advantage of this strategy over using a @RestController is precisely that it can be called from another class the same way you would call any other bean method.

    Note that all the topics here are within the scope of starter webflux dependency - I don't think you would need any others.