sse-eventbus is a Java library that sits on top of Spring's Sever-Sent Event support.
It keeps track of connected clients and broadcasts events to them.
Enable support by adding @EnableSseEventBus
to a Spring application.
@SpringBootApplication
@EnableSseEventBus
public class Application {
...
}
Create a controller that handles the SSE requests and returns a SseEmitter.
Each client has to provide an id that identifies this client.
The controller then registers the client in the eventBus with the method registerClient
and
subscribes it to events with the subscribe
method.
The SseEventBus class contains a convenient method createSseEmitter
that does all of this.
@Controller
public class SseController {
private final SseEventBus eventBus;
public SseController(SseEventBus eventBus) {
this.eventBus = eventBus;
}
@GetMapping("/register/{id}")
public SseEmitter register(@PathVariable("id") String id) {
SseEmitter emitter = new SseEmitter(180_000L);
emitter.onTimeout(emitter::complete);
this.eventBus.registerClient(id, emitter);
this.eventBus.subscribe(id, SseEvent.DEFAULT_EVENT);
return emitter;
//OR
//return this.eventBus.createSseEmitter(id, SseEvent.DEFAULT_EVENT)
}
}
On the client side an application interacts with the EventSource object. This object is responsible for sending the SSE request to the server and calling listeners the application registered on this object. As mentioned before the client has to send an id that should be unique among all the clients. A simple way is to use libraries like node-uuid that generates UUIDs.
const uuid = uuid();
const eventSource = new EventSource(`/register/${uuid}`);
eventSource.addEventListener('message', response => {
//handle the response from the server
//response.data contains the data line
}, false);
To broadcast an event to all connected clients a Spring application can either inject the SseEventBus
singleton and call the handleEvent
method
@Service
public class DataEmitterService {
private final SseEventBus eventBus;
public DataEmitterService(SseEventBus eventBus) {
this.eventBus = eventBus;
}
public void broadcastEvent() {
this.eventBus.handleEvent(SseEvent.ofData("some useful data"));
}
}
or use Spring's event infrastructure and publish a SseEvent
@Service
public class DataEmitterService {
private final ApplicationEventPublisher eventPublisher;
// OR: private final ApplicationContext ctx;
// this class implements the ApplicationEventPublisher interface
public DataEmitterService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void broadcastEvent() {
this.eventPublisher.publishEvent(SseEvent.ofData("some useful data"));
}
}
The library is hosted on the Central Maven Repository
<dependency>
<groupId>ch.rasc</groupId>
<artifactId>sse-eventbus</artifactId>
<version>2.0.0</version>
</dependency>
Simple demo application:
https://github.com/ralscha/sse-eventbus-demo
Ionic Demo Chat application:
https://github.com/ralscha/sse-eventbus-demo-chat
Articles about Server-Sent Events
SSE is supported in most browsers. The notable exceptions are the browsers from Microsoft IE and Edge.
http://caniuse.com/#feat=eventsource
Fortunately it is possible to polyfill the SSE support where it's missing.
Resolves Issue #13: Add lifecycle hooks
Resolves Issue #12: Hide ImmutableSseEvent completely from public API
Resolves Issue #8: Fix handling messages containing a new line character \n
Resolves Issue #6: Make members of DefaultSseEventBusConfiguration protected for easier sub classing
SseEventBusConfigurer.clientExpirationJobDelay
Extract subscription registry code out of the SseEventBus class into the interface SubscriptionRegistry and the class DefaultSubscriptionRegistry. This allows a project to customize the existing implementation or write their own implementation. To override the default implementation add a Spring managed bean of type SubscriptionRegistry to your project.
Example:
@Component
public class CustomSubscriptionRegistry extends DefaultSubscriptionRegistry {
@Override
public boolean isClientSubscribedToEvent(String clientId, String eventName) {
return super.isClientSubscribedToEvent(clientId, eventName)
|| super.isClientSubscribedToEvent(clientId, "*");
}
}
boolean completeAfterMessage = true;
eventBus.createSseEmitter("client1", 180_000L, true, completeAfterMessage, "event1", "event2");
Add support for automatic unregister clients from events during registering.
SseEventBus.createSseEmitter
supports an additional boolean parameter. If true the method
subscribes the client to the provided events and unsubscribes it from all other currently subscribed events.
eventBus.createSseEmitter("client1", 180_000L, true, "event1", "event2");
After this call the client is only subscribed to event1
and event2
.
...later in the application...
eventBus.createSseEmitter("client1", 180_000L, true, "event1");
After this call the client is only subscribed to event1
. The method automatically unregistered the client from event2
.
SseEvent.builder().event("eventName").data(dataObj).jsonView(JsonViews.PUBLIC.class).build()
ch.rasc.sse.eventbus.DataObjectConverter
changed.
Instead of the data
object the two methods receive the SseEvent
object.1.0.x: boolean supports(Object object); String convert(Object object);
1.1.x: boolean supports(SseEvent event); String convert(SseEvent event);
event.data()
.addExcludeClientId
method.
SseEvent.builder().addExcludeClientId("2")
.event("eventName")
.data("payload")
.build();
Code released under the Apache license.