Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring WebFlux (Flux): how to publish dynamically

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.

like image 980
John Zhang Avatar asked Jul 16 '18 21:07

John Zhang


1 Answers

Publish "dynamically" using 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)

Update For Reactor 3.4

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

like image 157
Oleh Dokuka Avatar answered Nov 05 '22 05:11

Oleh Dokuka