Server-sent Events

Share this article

  1. Introduction
  2. Subscribing to a stream: the EventSource object
  3. Sending events from the server
    1. Sending message events
    2. Sending custom events
    3. Managing reconnections with the retry interval
    4. Setting a unique identifier with the id field
  4. Handling events
  5. Handling errors
  6. Browser implementation discrepancies
  7. Browser support and fallback strategies

Imagine that your country’s national basketball team is playing for the World Basketball Championship. You want to keep track of the game, but you can’t watch it because it takes place while you’re at work.

Luckily for you, your national news service has a crackerjack web development team. They’ve built a sports ticker that updates with every foul called or basket scored. You visit a URL, and updates are pushed right to your browser. Of course, you wonder how they did that. The answer? Server-sent events.

Server-sent events are way of pushing data and/or DOM events from the server to a client using a stream. It’s handy for stock tickers, sports scores, flight tracking, e-mail notifications — any situation in which data will be updated periodically.

But wait! I hear you saying, Can’t we already do this with technologies like XMLHttpRequest, or Web Sockets? Well, yes. However doing so requires extending those objects to do what EventSource does natively.

Server-side considerations

Because server-sent events are streams of data, they require long-lived connections. You’ll want to use a server can handle large numbers of simultaneous connections. Event-driven servers are, of course, particularly well-suited to streaming events. These include Node.js, Juggernaut, and Twisted. For Nginx, there’s the nginx-push-stream-module. Server configuration is beyond the scope of this article, however, and it will vary with the server you use.

Let’s look at subscribing to a stream using an EventSource object. Then we’ll look at sending and handling events.

Subscribing to an event stream: the EventSource object

Creating an EventSource object is simple.

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

The EventSource constructor function accepts up to two parameters:

  • a URL string, which is required; and
  • an optional dictionary parameter that defines the value of the withCredentials property.

Dictionaries resemble objects in their syntax, but they are actually associative data arrays with defined name-value pairs. In this case, withCredentials is the only possible dictionary member. Its value can be true or false. (To learn more about dictionaries in general, refer to the Web IDL specification.)

Including the dictionary parameter is only necessary for cross-origin requests requiring user credentials (cookies). To date, no browser supports cross-origin EventSource requests. As a result, we won’t include the second parameter in our examples.

When the EventSource connection opens, it will fire an open event. We can define a function to handle that event by setting the onopen attribute.

var evtsrc = new EventSource('./url_of/event_stream/');
evtsrc.onopen = function(openevent){
    // do something when the connection opens
}

Should something go wrong with our connection, an error will be fired. 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.

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. This is the only way to handle custom events, as we’ll see in the Handling events section.

var onerrorhandler = function(openevent){
    // do something
}
evtsrc.addEventListener('error',onerrorhandler,false);

To close a connection use the close() method.

evtsrc.close();

So we’ve created our EventSource object, and defined handlers for the open, message, and error events. But in order for this to work, we need a URL that streams events.

Sending events from the server

A server-sent event is a snippet of text delivered as part of a stream from a URL. In order for browsers to treat our data as a stream we must:

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

The syntax for a server-sent event is simple. It consists of one or more colon-separated field name-value pairs, followed by an end-of-line character. Field names can contain one of four possible values.

  • data: The information to be sent.
  • event: The type of event being dispatched.
  • id: An identifier for the event to be used when the client reconnects.
  • retry: How many milliseconds should lapse before the browser attempts to reconnect to the URL.

Of these, only the data field is required.

Sending message events

In this example, we will send an event announcing which teams are playing in our championship game. When the browser receives this text, it will dispatch a message event.

data: Brazil v. United States

The value of the data field becomes the value of the message event’s data property. As mentioned above, server-sent events are message events by default. But as we’ll discuss in a bit, we can also dispatch custom events, by including an event field.

We can also send several pieces of data as a single event. Each chunk of data should be followed by an end-of-line character (either a new line character, carriage return character, or both). Here we’re appending an event that containing the location and attendance of this game.

data: Brazil v. United States

:Comments begin with a colon. Events must be followed a blank line.
data: Air Canada Centre
data: Toronto, Ontario, Canada
data: Attendance: 19,800

For this event, the value of the data property will be: Air Canada CentrenToronto, Ontario, CanadanAttendance: 19,800.

Take note of the blank line between 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

Events are of the type message unless we specify otherwise. To do that, we’ll need to include an event field. In the example that follows, we will add two startingfive events to our stream, and send our data 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’ll discuss the data property and MessageEvent interface in the Handling events section.

Managing connections and reconnections

Now while it is true that the server pushes events to the browser, the reality is a little more nuanced. If the server keeps the connection open, EventSource request will be one, extended request. If it closes, the browser will wait a few seconds, then reconnect. A connection might close, for example, if the URL sends an end-of-file token.

Each browser sets its own default reconnect interval. Most reconnect after 3 to 6 seconds. You can control this interval, however, by including a retry field. The retry field indicates the number of milliseconds the client should wait before reconnecting to the URL. Let’s build on our example from above and change our event to include a 5-second (5000 milliseconds) 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

Event streams can remain active for as long as the client is connected. Depending on your architecture and application, you may want the server to close connections periodically.

Setting a unique identifier with the id field

When the browser reconnects to the URL, it will receive whatever data is available at the point of reconnection. But in the case of a game ticker, we may want to let our visitor catch up on what he or she missed. This is why it’s a best practice to set an id for each event. In the example below, we’re sending an id as part of a score event.

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

Its value should be unique to the stream. In this case, we’re using the time the basket was scored.

The id field becomes the lastEventId property of this event object. But it serves another purpose. Should the connection close, the browser will include a Last-Event-ID header with its next request. Think of it as a bookmark for the stream. If the Last-Event-ID header is present, you can adjust your application’s response to send only those events that succeeded it.

Handling events

As mentioned above, all events are message events by default. Every message event has three attributes, defined by the MessageEvent interface.

event.data
Returns the data or message sent as part of the message event.
event.origin
Returns the origin of the message, which is typically a string containing the scheme (ex: http, https), host name, and port from which the message was sent.
event.lastEventId
Returns the unique identifier of the last event received.

Any time a message event is fired, our onmessage function will be invoked. This works just fine for applications in which you will only send message events. But its limitations become obvious if you’d like to send score or startingfive events as in our example. Using addEventListener is more flexible. In the code below, we’re handling a 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);

Handling errors

Smart error handling requires a bit more work than just setting the onerror attribute. We also need to know whether the error resulted in a failed connection, or a temporarily interrupted one. After a failed connection, the browser will not attempt to reconnect. If it is a temporary interruption — as can occur if the computer was asleep, or the server closes the connection — the browser will try again. Browsers will dispatch an error event for any of the following reasons.

  • The URL sends a Content-type response header with the wrong value.
  • The URL returned an HTTP error header such as 404 File Not Found or 500 Internal Server Error.
  • A network or DNS issue prevented a connection.
  • The server closed the connection.
  • The requesting origin is not one allowed by the URL.

That last point deserves some clarification. To date, no browser supports server-sent event requests across origins. In Firefox and Opera, attempting a cross-origin request will trigger an error event on the EventSource object, and the connection will fail. In Chrome and Safari, it will trigger a DOM security exception instead.

When handling errors, then, it’s important to check the readyState property. Let’s look at an example.

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 the code above, if the value of e.target.readyState is EventSource.CONNECTING (a constant defined by the specification; its value is 0), we will alert the user that we are reconnecting. If its value equals EventSource.CLOSED (another constant whose value is 2), we will alert the user that the browser will not reconnect.

Browser implementation discrepancies

Neither Firefox nor Opera change the EventSource object’s readyState property when the computer wakes from sleep mode. Even though the connection is temporarily lost, the value of EventSource.readyState remains 1. Chrome and Safari, by contrast, change the readyState value to 0, indicating that the browser is reestablishing the connection. In tests, however, all browsers appear to automatically reconnect to the URL several seconds after awaking.

Browser support and fallback strategies

As of publication, Opera 11.60+, Firefox 6.0+, Safari 5.0+, iOS Safari 4.0+, and Chrome 6.0+ all support server-sent events. Android’s WebKit and Opera Mini do not. Since EventSource is a property of the global object (in browsers, this is typically the window object), we can determine support using the following code.

if(window.EventSource !== undefined){
    // create an event source object.
} else {
    // Use a fallback or throw an error.
}

XMLHttpRequest can be used as a fallback for browsers that do not support EventSource. Polyfills that use an XHR fallback include EventSource by Yaffle and EventSource.js by Remy Sharp.

Keep in mind that when using XHR, your URL should ideally close the connection after each request. Doing so ensures maximum browser compatibility.

Of course, your application doesn’t exactly know whether the requesting object was EventSource or XMLHttpRequest, and therefore doesn’t know whether it should close the connection. To solve this issue, include a custom request header when using XMLHttpRequest as shown below.

var xhr = new XMLHttpRequest();
xhr.open('GET','./url_of/event_stream/');
xhr.setRequestHeader('X-Requestor','XHR');
xhr.send(null);

Then ensure that your application closes the connection when this custom header is present. 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.

Interlinked Nodes image via Shutterstock

Frequently Asked Questions (FAQs) about Server-Sent Events

What are the main differences between Server-Sent Events (SSE) and WebSockets?

Server-Sent Events and WebSockets are both communication protocols used in real-time web applications. However, they differ in several ways. SSE is a one-way communication protocol where the server sends updates to the client, but the client cannot send data to the server. On the other hand, WebSockets is a two-way communication protocol where both the client and the server can send data to each other. Additionally, SSE uses HTTP protocol, making it easier to implement and use with existing web technologies, while WebSockets uses its own protocol.

How can I handle connection errors in Server-Sent Events?

In Server-Sent Events, the EventSource API provides an ‘onerror’ event handler that you can use to handle connection errors. This event is triggered whenever an error occurs, such as when the connection is lost. You can use this event to implement reconnection logic or to notify the user about the connection issue.

Can I use Server-Sent Events with cross-origin requests?

Yes, Server-Sent Events can be used with cross-origin requests. However, you need to ensure that the server includes the appropriate CORS headers in its response. The EventSource API also provides a ‘withCredentials’ property that you can set to ‘true’ to include credentials like cookies in cross-origin requests.

How can I send data from the server to the client using Server-Sent Events?

In Server-Sent Events, the server can send data to the client by sending HTTP responses with a content type of ‘text/event-stream’. Each message in the response should be prefixed with ‘data: ‘ and should end with two newline characters. The client can then use the EventSource API to listen for these messages and handle them accordingly.

Can I use Server-Sent Events in all browsers?

Server-Sent Events are supported in most modern browsers, including Chrome, Firefox, and Safari. However, they are not supported in Internet Explorer. For applications that need to support all browsers, alternative technologies like WebSockets or long polling may be more suitable.

How can I close a Server-Sent Events connection?

You can close a Server-Sent Events connection by calling the ‘close’ method on the EventSource object. This will prevent the browser from reconnecting to the server when the connection is lost.

Can I send binary data with Server-Sent Events?

No, Server-Sent Events only support text data. If you need to send binary data, you should consider using other technologies like WebSockets or AJAX.

How can I handle different types of events with Server-Sent Events?

In Server-Sent Events, the server can specify the type of event by including an ‘event’ field in the message. The client can then use the ‘addEventListener’ method on the EventSource object to listen for these specific events.

Can I use Server-Sent Events with HTTP/2?

Yes, Server-Sent Events can be used with HTTP/2. However, due to the way HTTP/2 handles connections, there may be some differences in behavior compared to using SSE with HTTP/1.1.

How can I control the reconnection time in Server-Sent Events?

In Server-Sent Events, the server can control the reconnection time by including a ‘retry’ field in the message. This field should contain the reconnection time in milliseconds. The browser will then wait for this amount of time before attempting to reconnect to the server if the connection is lost.

Tiffany BrownTiffany Brown
View Author

Tiffany B. Brown is a freelance web developer and technical writer based in Los Angeles. Brown offers web development and consulting services to larger agencies and small businesses. A former member of the Opera Software developer relations team, Brown is also co-author of SitePoint's JumpStart HTML5 book. She sporadically writes about web development technology on her blog. You can follow her on Twitter at @webinista.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week