Java Server Sent Event - Automatically update web pages
Code source here: https://github.com/marco76/javaSSE
How a client (ex. web browser) can get updates from a server? Here some options:
1. Polling
The client regularly request to the server new data using a simple request/response via HTTP.
2. Websocket
Java EE 7 and Spring 4 implemented the WebSocket protocol that allows a bi-directional communication between a client and a server using TCP. The HTTP is used only for the handshake. WebSockets are the appropriate solution for application that need frequent exchange of small chunks of data at high speed (ex. trading, videogames, …).
3. Server-side events
HTML5 allows an unidirectional communication similar to the publish/subscribe model in JMS. The protocol used is HTTP and is described in the W3C documentation (note than IE is not compatible).
The events can be broadcasted multiple clients:
This solution is appropriate for application that require to be notified about new events (newsfeed, twitter-like, stock market etc.)
In Java the support for SSE is not yet a standard (it should be in Java EE 8). You can implement it using a Servlet or use some of the libraries that already support it : Jersey and Spring.
The following example uses Jersey in a Java EE environment (Weblogic).
Example
In this example we simulate the ‘live’ visualisation of a simple running competition. The result time (and the time of appearance) is randomly defined.
The class that produces the result is a standard REST java class. The resource must produce a SERVER_SENT_EVENTS type. The method return an EventOutput class. This object maintain the connection with the client and send the results with the write() method
@Path("competition")
public class SseEventOutput {
static Logger logger = Logger.getLogger("HelloSse.class");
// EventOutput is created when the client open this resource
// the method return the object EventOutput to the client (http)
// until when the EventOutput is not closed it can send data to the client using write()
EventOutput eventOutput = new EventOutput();
@Path("results")
@GET
@Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents() {
In the next fragment of code we show when the new data is sent to the client browser using the write method:
// prepare the OutboundEvent to send to the client browser
OutboundEvent event = buildOutboundEvent(runner);
logger.log(Level.INFO, "writing the result for the participant in position: " + position);
// the event is sent to the client
// the EventOutput is already returned to the client but until when is not closed it can send messages to the client
eventOutput.write(event);
// waiting for the next runner
position++;
The data to send is created and stored in the OutboundEvent object. In the object is defined in which format the data will be sent (JSON).
private OutboundEvent buildOutboundEvent(final Runner runner){
OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder();
eventBuilder.name(LocalTime.now() + " - runner at the finish ... ");
// the runner object will be converted to JSON
eventBuilder.data(Runner.class, runner);
eventBuilder.mediaType(MediaType.APPLICATION_JSON_TYPE);
return eventBuilder.build();
}
Here the result of the execution:
and the log:
While the connection was open Chrome showed the ‘working in progress’ icon:
Broadcast
In the class SSEBroadcast you can find an example of how the broadcast implementation works.
In the image you can see the result of sending 3 messages using post (green terminal) to the resource /api/hello_sse_broadcast/send.
The black terminal and chrome opened the page /api/hello_sse_broadcast/listen and they received the messages (chrome connected later and received only 2 messages).
The implementation of the broadcast require a Singleton:
@Path("hello_sse_broadcast")
@Singleton // singleton, one producer and multiple listener
public class SseBroadcast {
SseBroadcaster broadcaster = new SseBroadcaster();
We implemented one resource that return an EventOutput object. This object allows the communication between the server and the client. This resource is called by the client that subscribe the notifications/events :
/**
* When a client connect to this resource it opens a communication channel.
* @return
*/
@Path("listen")
@GET
@Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput listenData() {
final EventOutput eventOutput = new EventOutput();
this.broadcaster.add(eventOutput);
return eventOutput;
}
Another resource receive the POST messages and create an event to send to the subscribers:
/**
* For each call to this method some data is broadcasted to the listeners
* @return
*/
@POST
@Path("send")
public String sendData(@FormParam(value = "text") String text) {
OutboundEvent.Builder builder = new OutboundEvent.Builder();
builder.comment("optional comment: " + text);
builder.data(Calendar.getInstance().getTime().toString());
builder.mediaType(MediaType.APPLICATION_JSON_TYPE);
broadcaster.broadcast(builder.build());
return "date sent";
}
}
Interesting references:
https://streamdata.io/blog/push-sse-vs-websockets/