Server-Sent Events


Traditional HTTP message exchange initiated by a client application may prove insufficient if the application needs constant updates pushed from the server. One of possible solutions enabling the server push is the use of the Server-Sent Events API. The SSE methods and properties are implemented both in Firefox and WebKit-based browsers, though Internet Explorer 11 does not support this functionality. Server events are dispatched to the client according to the following model:

a client application willing to trigger the chain of updates from the server creates an instance of EventSource; the EventSource constructor accepts the URL of the server resource that will parse the request and start generating events;

the client builds its request so that it contains the Accept header with a special value: this is text/event-stream; by setting the value the client indicates that only text/event-stream content type is acceptable in the response;

on receiving the request the server sets the Content-Type header of the HTTP response to text/event-stream and sends the response; the response data must follow a special pattern;

the client parses the response body within the scope of predefined event handlers; there are three events that can be processed on the client side: these are open, message and error; event handlers are attached to the EventSource instance;

custom events handling is also supported: the server can indicate the name of a custom event and send associated data, the client must create an event listener for that particular event type.

The code snippets below illustrates the use of server-sent events in its elementary form. The client initiates the communication and utilizes the Accept header of the HTTP request to express the acceptable content type of the response. The server code is based on the web2py framework. The request is passed to the dispatcher, then it is mapped to the default action of the randomnumbers.py controller file. The server-side code sets required response type and generates a random value from the range [1..10000]. The response entity starts with data:; this is the field name indicating that any bytes after the colon should be treated as stream data. On receiving the response, the client parses received data in the named event handler:

import random
def index():
   response.headers['Content-Type'] = 'text/event-stream'
   return "data: "+str(random.randint(1, 10000))+"\n\n"

. . . client-side code . . .

var source=new EventSource("http://example.com/rng/randomnumbers/");
source.onmessage=showRandomNumbers;
function showRandomNumbers(event) {
 document.getElementById("random-numbers").innerHTML=event.data;
}

To inform the user about the state of the connection, the readyState property can be employed: if the client is connecting to the server, the readyState is set to 0; if the connection is open, the state is equal to 1; if the connection is closed, the property has the value of 2. Subsidiary event handlers enable further notifications:

source.onopen=function(event) {
 console.log("Your browser has an open connection");
}
source.onerror=function(event) {
 console.log("Unexpected error has occurred . . .");
 event.target.close();
}

Custom events will require additional tuning:

def custom_events():
   response.headers['Content-Type'] = 'text/event-stream'
   return "event: sum\ndata: "+str(random.randint(1,10000))+"\n\n"+"retry: "+str(1000)+"\n\n"

. . . client code . . .

var sum=0;
var source=new EventSource("http://example.com/rng/randomnumbers/custom_events/");
source.addEventListener("sum", addNumber, false);
function addNumber(event) {
 var number=parseInt(event.data);
 sum=sum+number;
 document.getElementById("sum").innerHTML=sum;
}

In the example above, the server creates a field with the event name: the field value will be interpreted by the client as the name of the custom event. The same name must be indicated in the event listener attached to the EventSource instance. Then the server appends the data related to the sum event. And the last field (retry) has a special meaning: it denotes the reconnection time - an interval in milliseconds that will be used by the client to reestablish the connection. Another way to maintain the uninterrupted data flow between the client and the server is to include the id field in the event stream: if the connection is broken, the client will send the Last-Event-ID header in its next request.