How to call SseEmitter just once from spring endpoint

Viewed 1032

I'm listening to my /score endpoint with JavaScript like so:

var sse = new EventSource('/score');
sse.onmessage = function (evt) {
    var el = document.getElementById('scores');
    el.appendChild(document.createTextNode(evt.data));
    el.appendChild(document.createElement('br'));
};

But for some reason it's like the endpoint it's called every second.

enter image description here

EventSource.onmessage documantation says:

Is an EventHandler called when a message event is received, that is when a message is coming from the source

This is my /score endpoint:

private ExecutorService executorService = Executors.newCachedThreadPool();

@GetMapping("/score")
public SseEmitter getScore() {
 final SseEmitter sseEmitter = new SseEmitter();

 executorService.submit(() -> {
  try {

   //System.out.println(text);
   //sseEmitter.send(text);
   sseEmitter.send("ok");
   sseEmitter.complete();


  } catch (Exception e) {
   e.printStackTrace();
  }
 });

 return sseEmitter;
}

How can i trigger it only when i send a request manually?

2 Answers

After a lot of reading i managed to trigger the /score endpoint on every request, but i had to change the server side a lot.

 @Autowired
    private MessageProcessor processor;

 @GetMapping(path = "/score", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<MyObject> receive() {
        return Flux.create(sink -> {
            processor.register(sink::next);
        });
    }

Now I'm returning a Flux object instead of a SseEmitter , because with Emitter i need constantly to send responses to the client.

I also created another endpoint /send , where i send my object with POST

 @PostMapping("/send")
    public String send(@RequestBody MyObject event) {
        LOGGER.info("Received '{}'", event);
        processor.process(event);
        return "Done";
    }

Nothing is changed on the Client side, the pipe between /receive and EventSource shouldn't be terminated from the client. I only added a JSON parsing because now i've got a custom object.

eventSource = new EventSource("/score");

eventSource.onmessage = function (evt) {
  var obj =JSON.parse (evt.data);
        
  var el = document.getElementById('scores');
   el.appendChild(document.createTextNode(obj.id+' '+obj.name+' '+obj.desc));
   el.appendChild(document.createElement('br'));
};

The key part was to use a MessageProccessor

@Service
public class MessageProcessor {

 private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessor.class);

 private List < Consumer < MyObject >> listeners = new CopyOnWriteArrayList < > ();

 public void register(Consumer < MyObject > listener) {
  listeners.add(listener);
  LOGGER.info("Added a listener, for a total of {} listener{}", listeners.size(), listeners.size() > 1 ? "s" : "");
 }

 // TODO FBE implement unregister

 public void process(MyObject event) {
  System.out.println("Processing: " + event);
  listeners.forEach(c -> c.accept(event));
 }
}

Another example of Webflux can be found here

Output

  • Client

enter image description here

  • Server (The 4 objects i posted)

enter image description here

That's because you never called EventSource.close() to close the connection. Since your eventSource doesn't have a terminating condition, your browser will repeatably call /score.

Related