Java Server Sent Event - Automatically update web pages

Updated:

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.

sse_1_2016-04-17_11-15-19

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, …).

sse_2_2016-04-17_11-15-29

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).

sse_3_2016-04-17_11-15-43

The events can be broadcasted multiple clients:

sse_4_2016-04-17_14-09-19

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:

2016-04-17_16-07-34_chrome

and the log:

2016-04-17_16-07-58_log

While the connection was open Chrome showed the ‘working in progress’ icon:

2016-04-17_16-06-05_chrome

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).

2016-04-17_16-25-22_clients

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/

You could be interested in

JavaEE CDI 2.0 and Observers


WebApp built by Marco using SpringBoot 3.2.4 and Java 21, in a Server in Switzerland