Implementing Push Technology Using Server-Sent Events

Making Ajax calls using the XmlHttpRequest object is a well established technique for generating dynamic server requests. However, Ajax does not allow the server to directly initiate data transfers to the client – a technique referred to as push technology. That’s where the server-sent events API comes into the picture. Specializing in push technology, server-sent events transmit data to clients as a continuous stream, referred to as an event stream, over a connection which is kept open. And, by maintaining an open connection, the overhead of repeatedly establishing a new connection is eliminated.

Comparison to WebSockets

Many people are completely unaware that server-sent events exist. This is because they are often overshadowed by the more powerful WebSockets API. While WebSockets enable bidirectional full duplex communication between the client and server, server-sent events only allow messages to be pushed to the client from the server. Applications that require near real-time performance, or two-way communication are probably better suited for WebSockets.

However, server-sent events also have certain advantages over WebSockets. For example, server-sent events support custom message types and automatic reconnection for dropped connections. These features can be implemented in WebSockets, but they are available by default with server-sent events. WebSockets applications also require servers that support the WebSockets protocol. By comparison, server-sent events are built atop HTTP and can be implemented in standard web servers.

Detecting Support

Server-sent events are relatively well supported, with Internet Explorer being the only major browser that does not yet support them. However, as long as IE lags behind, it will remain necessary to provide feature detection. On the client side, server-sent events are implemented using the EventSource object – a property of the global object. The following function detects whether or not the EventSource constructor is available in the browser. If the function returns true, then server-sent events can be used. Otherwise, a fallback mechanism such as long polling should be used.

function supportsSSE() {
  return !!window.EventSource;
}

Connecting

To connect to an event stream, call the EventSource constructor, as shown below. You must specify the URL of the event stream that you are attempting to subscribe to. The constructor will automatically take care of opening the connection.

EventSource(url);

The onopen Event Handler

When a connection is established, the EventSource‘s onopen event handler is invoked. The event handler takes the open event as its only argument. A generic onopen event handler is shown in the following example.

source.onopen = function(event) {
  // handle open event
};

EventSource event handlers can also be written using the addEventListener() method. This alternative syntax is preferred to onopen because it allows multiple handlers to be attached to the same event. The previous onopen event handler has been rewritten below, using addEventListener().

source.addEventListener("open", function(event) {
  // handle open event
}, false);

Receiving Messages

The client interprets an event stream as a series of DOM message events. Each event that is received from the server causes the EventSource‘s onmessage event handler to be triggered. The onmessage handler takes a message event as its only argument. The following example creates an onmessage event handler.

source.onmessage = function(event) {
  var data = event.data;
  var origin = event.origin;
  var lastEventId = event.lastEventId;
  // handle message
};

The message event contains three important properties ― data, origin, and lastEventId. As the name implies, data contains the actual message data, in string format. The data could potentially be a JSON string, which can be passed to the JSON.parse() method. The origin property contains the event stream’s final URL after any redirects. The origin should be checked to verify that messages are only received from expected sources. Finally, the lastEventId property contains the last message identifier seen in the event stream. The server can attach identifiers to individual messages using this property. If no identifier was ever seen, then lastEventId will be the empty string.

The onmessage event handler can also be written using the addEventListener() method. The following example shows the previous onmessage event handler, rewritten to use addEventListener().

source.addEventListener("message", function(event) {
  var data = event.data;
  var origin = event.origin;
  var lastEventId = event.lastEventId;
  // handle message
}, false);

Named Events

A single event stream can specify various types of events by implementing named events. Named events are not handled by the message event handler. Instead, each type of named event is processed by its own unique handler. For example, if an event stream contained events named foo, then the following event handler would be required. Notice that the foo event handler is identical to the message event handler, with the exception of the event type. Of course, any other types of named messages would require separate event handlers.

source.addEventListener("foo", function(event) {
  var data = event.data;
  var origin = event.origin;
  var lastEventId = event.lastEventId;
  // handle message
}, false);

Handling Errors

If a problem occurs with the event stream, the EventSource‘s onerror event handler is triggered. A common cause of errors is a disrupted connection. Although the EventSource object automatically attempts to reconnect to the server, an error event is also fired upon disconnection. The following example shows an onerror event handler.

source.onerror = function(event) {
  // handle error event
};

Of course, the onerror event handler can also be rewritten using addEventListener(), as shown below.

source.addEventListener("error", function(event) {
  // handle error event
}, false);

Disconnecting

An EventSource connection can be terminated by the client at any time by calling the close() method. The syntax for close() is shown below. The close() method does not take any arguments, and does not return any value.

source.close();

Connection States

The state of an EventSource connection is stored in its readyState property. At any point during its lifetime, a connection can be in one of three possible states – connecting, open, and closed. The following list describes each state.

  • Connecting – When an EventSource object is created, it initially enters the connecting state. During this time, the connection is not yet established. An EventSource will also transition into the connecting state if an established connection is lost. The readyState value for an EventSocket in the connecting state is 0. This value is defined as the constant EventSource.CONNECTING.
  • Open – An established connection is said to be in the open state. EventSource objects in the open state can receive data. A readyState value of 1 corresponds to the open state. This value is defined as the constant EventSource.OPEN.
  • Closed – An EventSource is said to be in the closed state if a connection is not established and it is not attempting to reconnect. This state is typically entered by calling the close() method. An EventSource in the closed state has a readyState value of 2. This value is defined as the constant EventSource.CLOSED.

The following example shows how the readyState property can be used to inspect an EventSource connection. To avoid hard coding the readyState values, the example makes use of the state constants.

switch (source.readyState) {
  case EventSource.CONNECTING:
    // do something
    break;
  case EventSource.OPEN:
    // do something
    break;
  case EventSource.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

Conclusion

This article has covered the client aspect of server-sent events. If you’re interested in learning more about server-sent events, I recommend reading The Server Side of Server-Sent Events. I’ve also written a more hands on article that covers Server-Sent Events in Node.js. Enjoy!

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Šime Vidas

    For a `EventSource` establishing a connection with a PHP script, e.g. `var es = new EventSource(“script.php”);`, how would the PHP script make sure that the connection stays open? By executing a loop with a `sleep();` in it?

    • http://www.cjihrig.com Colin Ihrig

      To my understanding, yes, that’s what you would do. However, there are a few things to consider, assuming you’re using PHP with Apache. First, Apache consumes a lot of resources to keep a connection open. On a busy server, this can really be a real problem. Second, Apache does its own buffering which can be outside of the developer’s control. I recommend using a server like Node.js.

  • Byron McMullen

    If the connection is lost and then reconnects is the open event fired again?

    • http://www.cjihrig.com Colin Ihrig

      Based on my experiments, it seems that an error event is fired on each connection loss and failed reconnect. The open event seems to fire on initial connection and successful reconnect.