Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring SSEEmitter Getting Completed Mid-way

I am new to Server Sent Events but not to Spring. Have made a controller which gets triggered from a button on the UI which initiates SSEEmitter and passed that to another thread which in loop sends message to UI after each 4 seconds. SO far i am running a loop of 10 which sleeps for 4 seconds each but suddenly around iteration of 6 or 7th loop, I get exception "Exception in thread "Thread-4" java.lang.IllegalStateException: ResponseBodyEmitter is already set complete"..

Hence, event source again re-establishes the connection i.e. calls the controller method again which certainly i do not want.

I am here trying a simple thing.. User subscribes by clicking on the button.. Server send response 10 or 20 whatever times to the browser. And as far as I think this is what SSE created for.

Code below.:

@RequestMapping("/subscribe")
public SseEmitter subscribe() {
    SseEmitter sseEmitter = new SseEmitter();
    try {
        sseEmitter.send("Dapinder");
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    Runnable r = new AnotherThread(sseEmitter);
    new Thread(r).start();
    return sseEmitter;
}

public class AnotherThread implements Runnable {

private SseEmitter sseEmitter;

public AnotherThread(SseEmitter sseEmitter) {
    super();
    this.sseEmitter = sseEmitter;
}

@Override
public void run() {
    SseEventBuilder builder = SseEmitter.event();
    builder.name("dapEvent");
    for (int i = 0; i < 10; i++) {
        builder.data("This is the data: " + i +" time.");
        try {
            //sseEmitter.send(builder);
            sseEmitter.send("Data: "+i);
            //sseEmitters.get(1L).send("Hello");
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    sseEmitter.complete();
}

public SseEmitter getSseEmitter() {
    return sseEmitter;
}

public void setSseEmitter(SseEmitter sseEmitter) {
    this.sseEmitter = sseEmitter;
}

}

function start() {
    var eventSource = new EventSource("http://localhost:8080/HTML5SSE/springSSE/subscribe"); //  /springSSE/connect
      eventSource.onmessage = function(event) {
        document.getElementById('foo').innerHTML = event.data;
    };

}


<button onclick="start()">Subscribe</button>
like image 838
Dapinder Singh Avatar asked Mar 23 '26 19:03

Dapinder Singh


2 Answers

Your builder is not being used; you create and configure a builder, but then you send a plain message with 'sseEmitter.send' directly. Try this:

sseEmitter.send(SseEmitter.event().name("dapEvent").data("This " + i +" time.");

One more thing: Why do you call the send method already in the subscribe method? At this point in time, the SseEmitter has not been returned. Is this message coming through to the client?

Here is an excellent article explaining SSE from the JavaScript perspective (not Spring). You will see here that you can cancel the event stream from the client by calling close on the stream. Combine this with an event listener, and you should have what you need:

var source = new EventSource('...');
source.addEventListener('error', function(e) {
  if (e.currentTarget.readyState == EventSource.CLOSED) {
    // Connection was closed.
  } else {
    // Close it yourself
    source.close();
  }
});
source.addEventListener('message', function(e) {
  console.log(e.data);
});

Note: The article says e.readyState, however I think this is wrong. The received object e is an Event. You need to get the EventSource object from it like this: e.currentTarget.

like image 149
Leif John Avatar answered Mar 25 '26 07:03

Leif John


You need to use the second constructor of SseEmitter which takes a Long timeout argument. Please refer the code below -

@RequestMapping("/subscribe")
public SseEmitter subscribe() {
  SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE) // for maximum timeout

Below is the copy of Java-doc of this constructor -

/**
 * Create a SseEmitter with a custom timeout value.
 * <p>By default not set in which case the default configured in the MVC
 * Java Config or the MVC namespace is used, or if that's not set, then the
 * timeout depends on the default of the underlying server.
 * @param timeout timeout value in milliseconds
 * @since 4.2.2
 */

I think the default timeout of SSE connection in Tomcat is 40 seconds. Not sure though.

like image 41
V.Vidyasagar Avatar answered Mar 25 '26 07:03

V.Vidyasagar