Server Sent Events
Server Sent Events (SSE) allow you to publish data over a long-lived response to a client.
To publish, create a handler and then create an SsePublisherpublisher using the SsePublisher.start(request, response)
method.
The following example creates a thread that will send 1000 messages to the browser and then close the stream:
public class ServerSentEventsExample {
public static void main(String[] args) {
MuServer server = httpServer()
.addHandler(Method.GET, "/sse/counter", (request, response, pathParams) -> {
SsePublisher publisher = SsePublisher.start(request, response);
new Thread(() -> count(publisher)).start();
})
.addHandler(ResourceHandlerBuilder.fileOrClasspath("src/main/resources/samples", "/samples"))
.start();
System.out.println("Open " + server.uri().resolve("/sse.html") + " to see some numbers.");
}
public static void count(SsePublisher publisher) {
for (int i = 0; i < 100; i++) {
try {
publisher.send("Number " + i);
Thread.sleep(1000);
} catch (Exception e) {
// The user has probably disconnected so stopping
break;
}
}
publisher.close();
}
}
(see full file)
This page connects to the endpoint above and prints each message as it comes in:
<p>Connection status: <span class="status">Not started. <input type="button" value="start" id="startButton"></span></p>
<p>Messages:</p>
<div class="messages"></div>
<script>
document.getElementById('startButton').addEventListener('click', _ => {
let $ = document.querySelector.bind(document);
let status = $('.status');
let messages = $('.messages');
let source = new EventSource('/sse/counter');
source.addEventListener('open', e => {
console.log('Connected', e);
status.textContent = 'Connected';
});
source.addEventListener('error', e => {
console.log('error', e);
status.textContent = 'Error';
});
source.addEventListener('message', e => {
messages.appendChild(document.createTextNode(e.data));
messages.appendChild(document.createElement('br'));
});
});
</script>
(see full file)
Note that in these examples a thread is kept open per client, and each call to
send
is blocking. See AsyncSsePublisherfor an async version that has callbacks.
Try it out
Connection status: Not started.
Messages:
JAX RS SSE Publishing
If using JAX-RS resources, you can use an SSE broadcaster to broadcast to multiple clients.
In the following example, there is a single incrementing counter and clients can register to updates to the counter. Note that no matter how many clients are connected, they will all share the same counter and only a single thread is used.
public class JaxRSBroadcastExample {
public static void main(String[] args) {
TimeResource timeResource = new TimeResource();
timeResource.start();
MuServer server = httpServer()
.addHandler(RestHandlerBuilder.restHandler(timeResource))
.addHandler(ResourceHandlerBuilder.fileOrClasspath("src/main/resources/samples", "/samples"))
.start();
System.out.println("Example started at " + server.uri().resolve("/sse.html"));
}
}
@Path("/sse/counter")
class TimeResource {
long count = 0;
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
Sse sse = MuRuntimeDelegate.createSseFactory();
SseBroadcaster broadcaster = sse.newBroadcaster();
@GET
@Produces("text/event-stream")
public void registerListener(@Context SseEventSink eventSink) {
broadcaster.register(eventSink);
}
public void start() {
executor.scheduleAtFixedRate(() -> {
count++;
String data = "Number " + count;
broadcaster.broadcast(sse.newEvent(data));
}, 0, 100, TimeUnit.MILLISECONDS);
}
}
(see full file)
The same HTML as for the previous example can be used.