HTML5 SSE (streaming data)

How to stream data from a web server to browser application.

These applications here use Python on web server and straight JavaScript

Before getting into SSE I thought it worth looking at the merits of three comms models in the web context

  • Polling

  • SSE

  • WebSocket

Visit this website for an interesting write up on the differences. Clearly for what we are doing in the Case Study, SSE is the most appropriate solution for the comms layer between the browser and the webserver. For the comms layer solution between the backend and webserver we are still undecided.

Server Side Events (SSE)

The idea behind SSEs may be familiar: a web app "subscribes" to a stream of updates generated by a server and, whenever a new event occurs, a notification is sent to the client. Created to overcome limitations of AJAX - polling (if data not available server returns an empty response - HTTP overhead), long polling (if the server does not have data available, the server holds the request open until new data is made available, "hanging GET" - implementation includes "hacks" and appending script tags to an 'infinite' iframe)

Server-Sent Events have been designed from the ground up to be efficient. When communicating using SSEs, a server can push data to your app whenever it wants, without the need to make an initial request. SSEs open a single unidirectional channel between server and client (unidirectional “push”), unlike long polling, SSEs are handled directly by the browser and the user simply has to listen for messages.

SSEs are better than WebSockets in scenarios when the flow of data is only in one direction - from the server to the client. SSEs are sent over traditional HTTP - that means they do not require a special protocol or server implementation to get working. Additional features like automatic reconnection, event IDs, ability to send arbitrary events.

SSE is a native HTML5 feature that allows the server to keep the HTTP connection open and push data changes to the client.

JavaScript API

To subscribe to an event stream, create an EventSource object and pass it the URL of your stream.

if (!!window.EventSource) { var source = new EventSource('stream.php'); } else { // Result to xhr polling :( }

Note: If the URL passed to the EventSource constructor is an absolute URL, its origin (scheme, domain, port) must match that of the calling page.

var source = new EventSource("http://localhost:8000/hello");



Next, set up a handler for the message event. You can optionally listen for open and error:

source.addEventListener('message', function(e) { console.log(e.data); }, false); source.addEventListener('open', function(e) { // Connection was opened. }, false); source.addEventListener('error', function(e) { if (e.readyState == EventSource.CLOSED) { // Connection was closed. } }, false);


When updates are pushed from the server, the onmessage handler fires and new data is be available in its e.data property. The magical part is that whenever the connection is closed, the browser will automatically reconnect to the source after ~3 seconds. Your server implementation can even have control over this reconnection timeout.

Browser Support

First browser version supporting SSE:

Chrome 6.0, Mozzilla 6.0, Safari 5.0, Opera 11.5

! NOT SUPPORTED IN Internet Explorer !



Event Stream Format

Sending an event stream from the source is a matter of constructing a plaintext response, served with a text/event-stream Content-Type, that follows the SSEformat. In its basic form, the response should contain a "data:" line, followed by your message, followed by two "\n" characters to end the stream:

 

If your message is longer, you can break it up by using multiple "data:" lines. Two or more consecutive lines beginning with "data:" will be treated as a single piece of data, meaning only one message event will be fired. Each line should end in a single "\n" (except for the last, which should end with two). The result passed to your message handler is a single string concatenated by newline characters. For example:

 

This will produce "first line\nsecond line" in e.data. One could then use e.data.split('\n').join('') to reconstruct the message sans "\n" characters.

Example using JSON data:


with the following client-side code:


Additional information about Event

You can send a unique id with an stream event by including a line starting with "id:"

Setting an ID lets the browser keep track of the last event fired so that if, the connection to the server is dropped, a special HTTP header (Last-Event-ID) is set with the new request. This lets the browser determine which event is appropriate to fire. The message event contains a e.lastEventId property.

Changing the reconnection time-out can be done by including a line beginning with "retry:", followed by the number of milliseconds to wait before trying to reconnect.

In case of different event types being generated by one event source, including a line "event:" followed by a unique name will associate this event with that name. On the client, an event listener can be setup to listen to that particular event. Note that the fields do not have to be in any order as long as there is a newline (\n) for each field and two (\n\n) at the end of them. Example of server output with three different event types:

 

The corresponding event listener can look like this:

 

Close connection

To cancel a stream from the client, simply call:

To cancel a stream from the server, respond with a non "text/event-stream"Content-Type or return an HTTP status other than 200 OK (e.g. 404 Not Found). Both methods will prevent the browser from re-establishing the connection.

Security

From he WHATWG's section on Cross-document messaging security:

Authors should check the origin attribute to ensure that messages are only accepted from domains that they expect to receive messages from. Otherwise, bugs in the author's message handling code could be exploited by hostile sites.

So, as an extra level of precaution, be sure to verify e.origin in your message handler matches your app's origin:


Another good idea is to check the integrity of the data you receive.

Python Generators and SSE

Python generators are a great fit to create a server stream because they can keeping looping and yielding data and handing it to the client very seamlessly. Here is an example code from a flask web application. The stream contains timestamp data generated every second

Python code to consume a stream of data

Useful websites

https://medium.com/code-zen/python-generator-and-html-server-sent-events-3cdf14140e56

https://www.html5rocks.com/en/tutorials/eventsource/basics/

https://pypi.org/project/sseclient-py/

https://www.w3schools.com/html/html5_serversentevents.asp