Introduction to server-sent events:

In a simple HTTP request-response scenario a user opens a connection & sends a HTTP request to the server (for example a HTTP GET request),& then receives a HTTP response back from the server and then server closes the connection with the user once the response is fully sent/received. This SSE connection always starts by a user when the user requests all the data. Actually, Server-Sent Events (SSE) is a mechanism that enables server to asynchronously send the data from the server to the user once the user-server connection is established by the user. Once the user established the connection, the server continuously sends data and decides to send it to the user whenever a new "piece" of data is available. 

Imagine that your country’s national football team is playing for the World football Championship & unfortunately you can’t watch it but still want to keep track of the event. Then SSE is the elixir for you in such bad times, your national news service may have built a sports portal that updates you with every goal. You visit its URL and then the real magic begins the SSE send live updates directly to your browser. You always wonder how they did that & the answer is Server-sent events...
The Server keeps on sending data-events to the user, whenever new “chunk” of data is available on the server. That is why it got the name Server-Sent Events.

Server-side considerations

Because SSEs are streams of data, it is important for the client/user to have long-lived connections. You are going to use a server which can handle large numbers of simultaneous connections.

Event-driven servers are, of course, well-congruent to streaming events. These include Juggernaut, Node.js, and Twisted. There is a nginx-push-stream-module for Nginx,. Server configuration is not a part of this article; however, it can vary with the server used by the client.

Possible Applications #

A few examples of applications using of SSE:

  • You can see SSE in a real-time chart streaming live stock prices
  • A live Twitter/Fb wall receiving live updates by Twitter’s streaming API
  • A monitor for server stats like health ,uptime, and other running processes
  • A sports portal using SSE push live updates right to your browser

Overview of the API #

The main points of interest:

new EventSource(url) - It creates  EventSource object, which immediately starts looking for new events on the visited URL.

readyState - It is for the EventSource & tells us whether we are connecting (0), open (1), or closed (2).

onopen - When the EventSource connection opens it will send an open event to the client's browser. Event can be handled by defining a function by setting the onopen attribute.

 onmessage - By default, Streamed events are message events. onmessage use to define a handler function in order to manage message events.

addEventListener -  We can also use addEventListener() to listen for new events & it is the only way to handle custom events.

event.data - It Returns the data or message sent to the client as part of the message event.

close - it closes the connection from the client/user side.

Simple Examples #

var source = new EventSource('/stats'); source.onopen = function () { connectionOpen(true); }; 

source.onerror = function () { connectionOpen(false); }; 

source.addEventListener('connections', updateConnections, false);

source.addEventListener('requests', updateRequests, false); source.addEventListener('uptime', updateUptime, false); 

source.onmessage = function (event) { // a message without a type was fired };

Let’s move towards subscribing to a stream using an “EventSource” object. After that, we’ll march on towards sending and handling events.

How to subscribe an event stream: the EventSource object

It is easy to create EventSource object:

var evtsrc = new EventSource('./url_of/event_stream/',{withCredentials:false});

The EventSource constructor feature accepts only two parameters:

  • A URL string, which is required; and
  • An optional dictionary parameter which shows the value of the withCredentials property

Dictionaries look alike objects in their syntax, but In fact, they are osculant data shown with defined name-value pairs. But, in this case, withCredentials is the only available dictionary member. It can have two possible values true or false.

User’s credentials (cookies) is necessary for cross-origin requests, that’s why, no browser supports the cross-origin EventSource requests, till today.

When the EventSource connection opens, it will send an open event. To handle the event, we can define a function by setting the onopen attribute:

var evtsrc = new EventSource('./url_of/event_stream/');

evtsrc.onopen = function(openevent){

    // do something when the connection opens

}

What if something went wrong with server/client connection, an error will be send by the function. We can define a handler function for these events using the onerror attribute. We’ll discuss some causes of error events in the Handling errors section.
To handle these types of events we can define a handler function by using the onerror attribute.
In next section, we will discuss some reason behind these errors.

evtsrc.onerror = function(openevent){

    // do something when there's an error

}

Streamed events are message events by default. To handle message events, we can use the onmessage attribute to define a handler function.

evtsrc.onmessage = function(openevent){

    // do something when we receive a message event.

}

We can also use addEventListener() to listen for events.

var onerrorhandler = function(openevent){

    // do something

}

evtsrc.addEventListener('error',onerrorhandler,false);

To close a connection use the close() method.

evtsrc.close();

Now we’ve created our EventSource object, and defined handlers for the events like openmessage, and error. But to make it working, we need a URL that streams events.

Sending events from the server

A server-sent event delivered from a URL as a part of a stream, is actually a snippet of text.
To make data to be treated as a stream by browsers, we must:

  • define our content with a Content-type header having value  text/event-stream;
  • use UTF-8 character encoding.

The syntax for a server-sent event is simple & has one or more colon-separated field name-value pairs which followed up by an end-of-line character.
These Field name can contain one of four possible values:

  • data: The information to be sent.
  • event: The type of event to be sent.
  • id: An identifier used by the event when the client reconnects.
  • retry: How much time (in milliseconds) should the browser wait to make another attempt to reconnect to the URL.

Of these, only the data field is important.

Sending message events

We can understand this procedure with a simple example, for example we send an event declaring the teams that are playing in the championship game. When the browser gets this information, it sends out a message event.

data: Brazil v. United States

The data field’s value turns out to be the value of the data property of the message event. As discussed earlier, by Default; server-sent events are message events. But we can also send out custom events with the help of including an event field.

Numerous pieces of data can also be sent as a single event. Each piece of data should be succeeded by EOL character (either a carriage return character, new line character or both can be placed). Here we are adding an event that is holding the attendance and the location of this game.

data: Brazil v. United States

:Comments begin with a colon. A blank line must follow events.

data: Air Canada Centre

data: Toronto, Ontario, Canada

data: Attendance: 19,800

For this particular event, he data property’s value will be : Air Canada CentrenToronto, Ontario, CanadanAttendance: 19,800.

NOTE: Pay attention to the blank line between the events. In order for the client to receive an event, it must be followed by a blank line. Comments begin with a colon.

Sending custom events

Generally, event is of type message unless we do not specify custom value for it. For this purpose, we will embrace an event field to it. In the following example, we will be adding two startingfive events to our stream and our data will be sent as a JSON-formatted string.

event: startingfive

data: {"team":{"country":"Brazil","players":[{"id":15,"name":"de Sousa","position":"C"},{"id":12,"name":"Dantas","position":"F"},

{"id":7,"name":"Jacintho","position":"F"},{"id":6,"name":"de Oliveira Ferreira","position":"G"},{"id":4,"name":"Moisés Pinto","position":"G"}]}}

event: startingfive

data: {"team":{"country":"USA","players":[{"id":15,"name":"Charles","position":"C"},{"id":11,"name":"Cash","position":"F"},

{"id":5,"name":"Jones","position":"F"},{"id":7,"name":"Montgomery","position":"G"},{"id":4,"name":"Pondexter","position":"G"}]}}

Here we need to listen for the startingfive event instead of a message event. Our data field, however, will still become the value of the event’s data property.

We should listen for the startingfive event and not for a message event. Our data field will acts as the value of the data property of the event.

We will further discuss about the data property and MessageEvent interface in the events handling section.

Managing connections and reconnections

Now it is clear that the server pushes events to the user’s browser, but there is much more in this function.  If the server keeps the connection with client open, EventSource request will extend request to the user. If the server closes the connection, the browser will wait for few seconds; then it will try to reconnect if there is no big issue from the client’s side.

Every browser has its own fixed reconnect interval. Most of the browser sends reconnect request after 3 to 6 seconds. It can be changed with your need by including a retry field. The retry field sets the number of milliseconds the client should wait before trying to reconnect to the url. Below is the example defining how to include a 5-second retry interval.

event: startingfive
data: {"team":{"country":"USA","players":[{"id":15,"name":"Charles","position":"C"},{"id":11,"name":"Cash","position":"F"},
{"id":5,"name":"Jones","position":"F"},{"id":7,"name":"Montgomery","position":"G"},{"id":4,"name":"Pondexter","position":"G"}]}}
retry: 5000

Server-client connection will remain active as long as the client is connected. You can set your server to close automatically for a period of time and connect again.

Setting a unique identifier with the id field

On reconnecting to the URL, the browser will receive any new data available at the time of reconnection. But if anyhow, user missed any new data, we will let our user know what he or she missed. This is why we set an id for each event.
In the example below, we are sending a unique id as a part of a score event of a game.

event: score
retry: 3000
data: Brazil 14
data: USA 13
data: 2pt, de Sousa
id: 09:42

Use unique numbers to set them as event’s id. In this example, we are using the time at which the goal was scored in the game.

The id field is the lastEventId. If the browser closes the connection then it will include a Last-Event-ID header with request to connect again. You can set your application’s response as per your need like sending only successful events.

Handling events

As we know, by default, all events are message events. There are three attributes every message event has which are defined by the MessageEvent.

event.data

Returns message/data sent as part of the message event.

event.origin
It returns the origin of the message like port from which the message was dispatched.

event.lastEventId

It returns the unique id of the last event received.

If a message event is fired, the onmesage event will be invoked. It is good for applications which will send only message events. But sending score or startingfive events as we explain in our example, its limitations make our work more complex. Using addEventListener is more reliable. In the example below, we are working on startingfive event using addEventListener.

var evtsrc = new EventSource('./url_of/event_stream/');

var startingFiveHandler = function(event){

    var data = JSON.parse(event.data), numplayers, pl;

      console.log( data.team.country );

      numplayers = data.team.players.length;

      for(var i=0; i < numplayers; i++){

        pl = '['+data.team.players[i].id+'] '+data.team.players[i].name+', '+data.team.players[i].position;

        console.log( pl );

    }

}

 evtsrc.addEventListener('startingfive',startingFiveHandler,false);

Error Handling

Smart error handling, needs some extra efforts than just setting the onerror attribute. We should have to identify whether a failed connection will be the result of error, or it resulted in a temporarily interrupted connection. If it results in a failed connection, the browser will not make effort to reconnect. It can result in a temporary interruption if the computer was asleep, or the connection was closed by the server, in this situation the browser will try again to reconnect. Browsers will send out an error event for any of the following reasons.

  • A Content-type response header consisting wrong value is sent by the URL.
  • An HTTP error header (it can be 404 File Not Found or 500 Internal Server Error) is returned by the URL.
  • A connection is prevented by a network or DNS issue.
  • Connection is closed by the server.
  • The requesting origin is not one allowed by the URL.

That last point needs some clarification. Till today, there is not a single browser made which can support server-sent event requests cross origin.

In some popular browsers like Firefox & Opera, when the client attempt to send cross-origin request, an error event will occur resulting in failing connection.

In other browsers like Google Chrome & Safari, you will get a DOM security exception on doing same.

It is important to check the readyState property when handling errors. Consider an example to make this clear.

var onerror = function(event){

    var txt;

    switch( event.target.readyState ){

        // if reconnecting

        case EventSource.CONNECTING:

            txt = 'Reconnecting...';

            break;

        // if error was fatal

        case EventSource.CLOSED:

            txt = 'Connection failed. Will not retry.';

            break;

    }

    alert(txt);

}

In this example, if EventSource.CONNECTING (a constant defined by specification whose value is 0) is the value of e.target.readyState, we will alert the user that we are reconnecting. If its value is equals to EventSource.CLOSED (another constant; its value is 2), the user will get an alert that the browser will not reconnect.

Browser implementation discrepancies

When the system wakes from sleep mode, the readyState property of the EventSource object is not changed in Firefox as well as in Opera. However the connection is lost for a moment, the value of EventSource.readyState still remains 1. But in Safari and Chrome, the value of the readyState is changed to 0, that represent the connection has been re-established by the browser. In tests, even though; all browsers seem to reconnect to the URL automatically; quite a few seconds after awakening.

Browser support and fallback strategies

Server-sent events are supported by all browsers like Opera 11.60+, Safari 5.0+, iOS, Firefox 6.0+, Safari 4.0+, and Chrome 6.0+ except Android’s WebKit and Opera Mini. Since EventSource is considered as a property of the global object (this is typically the window object in browsers), support can be determined by using the following code:

if(window.EventSource !== undefined) {

    // create an event source object.

} else {

    // Use a fallback or throw an error.

}

For browsers that do not support EventSource, we can use XMLHttpRequest as a fallback. Polyfills that use an XHR fallback enclose EventSource by Yaffle and EventSource.js by Remy Sharp.

Do remember, when using XHR, after each request your URL should preferably close the connection. By doing this we can ensures maximum browser compatibility.

Naturally, your application doesn’t exactly know about the requesting object if it was an EventSource or XMLHttpRequest, so it doesn’t know whether it the connection should be closed or not. To resolve this issue you can include a custom request header when using XMLHttpRequest. Please consider the example shown below:

var xhr = new XMLHttpRequest();

xhr.open('GET','./url_of/event_stream/');

xhr.setRequestHeader('X-Requestor','XHR');

xhr.send(null);

When you use this custom header, make sure your application closes the connection. You can do this by setting the value of the Content-type: header to text/plain, and (optionally) including a Connection: close header in the URL’s response.

 

About The Author: Hi! I am Neelam Y. I am passionate about research and technology. Whether it is website designing/development, content writing or internet marketing; I have a solid track record of delivering utmost satisfaction to my clients. If you want me to hire for your job, you can contact me on upwork.com: https://www.upwork.com/users/~01ea293264ce65c7ea