I am new to Reactive programming and Spring WebFlux. I want to make my App 1 publish Server Sent event through Flux and my App 2 listen on it continuously.
I want Flux publish on-demand (e.g. when something happens). All the example I found is to use Flux.interval to periodically publish event, and there seems no way to append/modify the content in Flux once it is created.
How can I achieve my goal? Or I am totally wrong conceptually.
FluxProcessor
and FluxSink
One of the techniques to supply data manually to the Flux
is using FluxProcessor#sink
method as in the following example
@SpringBootApplication @RestController public class DemoApplication { final FluxProcessor processor; final FluxSink sink; final AtomicLong counter; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } public DemoApplication() { this.processor = DirectProcessor.create().serialize(); this.sink = processor.sink(); this.counter = new AtomicLong(); } @GetMapping("/send") public void test() { sink.next("Hello World #" + counter.getAndIncrement()); } @RequestMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent> sse() { return processor.map(e -> ServerSentEvent.builder(e).build()); } }
Here, I created DirectProcessor
in order to support multiple subscribers, that will listen to the data stream. Also, I provided additional FluxProcessor#serialize
which provide safe support for multiproducer (invocation from different threads without violation of Reactive Streams spec rules, especially rule 1.3). Finally, by calling "http://localhost:8080/send" we will see the message Hello World #1
(of course, only in case if you connected to the "http://localhost:8080" previously)
With Reactor 3.4 you have a new API called reactor.core.publisher.Sinks
. Sinks
API offers a fluent builder for manual data-sending which lets you specify things like the number of elements in the stream and backpressure behavior, number of supported subscribers, and replay capabilities:
@SpringBootApplication @RestController public class DemoApplication { final Sinks.Many sink; final AtomicLong counter; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } public DemoApplication() { this.sink = Sinks.many().multicast().onBackpressureBuffer(); this.counter = new AtomicLong(); } @GetMapping("/send") public void test() { EmitResult result = sink.tryEmitNext("Hello World #" + counter.getAndIncrement()); if (result.isFailure()) { // do something here, since emission failed } } @RequestMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent> sse() { return sink.asFlux().map(e -> ServerSentEvent.builder(e).build()); } }
Note, message sending via Sinks
API introduces a new concept of emission
and its result. The reason for such API is the fact that the Reactor extends Reactive-Streams and has to follow the backpressure control. That said if you emit
more signals than was requested, and the underlying implementation does not support buffering, your message will not be delivered. Therefore, the result of tryEmitNext
returns the EmitResult
which indicates if the message was sent or not.
Also, note, that by default Sinsk
API gives a serialized version of Sink
, which means you don't have to care about concurrency. However, if you know in advance that the emission of the message is serial, you may build a Sinks.unsafe()
version which does not serialize given messages
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With