APIs: Server Sent Events - JUMP START HTML5 (2014)

JUMP START HTML5 (2014)

Chapter 29 APIs: Server Sent Events

Server Sent Events is an API by which browsers receive push notification from servers through the HTTP connection. Once a connection is established, the server can send updates to the client periodically as new data is available. In other words, a client subscribes to the events generated on the server side. Once a particular event occurs, the server sends a notification to the client. With Server Sent Events (SSEs), you can create rich applications that provide real-time updates to the browser without significant HTTP overhead.

In this chapter, we’ll discuss the concepts behind SSEs and learn how to use them to build real-time HTML5 apps.

The Motivation for SSEs

Before moving any further, it’s important to understand the need for this API. To explain, let me first ask you a question: how would you design a real-time app that continuously updates the browser with new data if there were no Server Sent Events API? Well, you could always follow the traditional approach of long polling through setInterval()/setTimeout() and a little bit of Ajax. In this case, a callback executes after a specified time interval and polls the server to check if new data is available. If data is available. it's loaded asynchronously and the page is updated with new content.

The main problem with this approach is the overhead associated with making multiple HTTP requests. That’s where SSEs come to rescue. With SSEs, the server can push updates to the browser as they’re made available through a single unidirectional connection. And even if the connection drops, the client can reconnect with the server automatically and continue receiving updates.

Keep in mind that SSEs are best suited for applications that require unidirectional connection. For example, the server may obtain latest stock quotes periodically and send the updates to the browser, but the browser doesn’t communicate back to the server, it just consumes the data sent by server. If you want a bi-directional connection, you’ll need to use Web Sockets, which are covered in the next chapter.

Okay, enough talking. Let’s code!

The API

Here’s some example code showing the use of SSEs:

if (!!window.EventSource) {

var eventsource=new EventSource('source.php');

eventsource.addEventListener('message',function(event) {

document.getElementById("container").innerHTML = event.data;

}, false);

}

else{

// fallback to long polling

}

Note: Using Modernizr

Note that we’re checking for browser support of SSEs. The same check can be achieved using Modernizr:

if (Modernizr.eventsource) {

// proceed further

}

else{

// fallback to long polling

}

To use SSEs, you just call the EventSource constructor, passing the source URL. The source may be any back-end script that produces new data in real time. Here I have used a PHP script (source.php), but any server-side technology that supports SSEs can be used.

You can then attach event listeners to the eventsource object. The message event is fired whenever the server pushes some data to the browser and the corresponding callback is executed. The callback accepts an event object and its data property contains our data from the server. Once you have the data, you can perform tasks such as updating a part of the page with new information automatically in real time.

You can be aware of when the connection opens and when an error occurs, as follows:

eventsource.addEventListener('open', function(event) {

// connection to the source opened

},false);

eventsource.addEventListener('error', function(event) {

// Bummer!! an error occurred

},false);

The EventStream Format

There needs to be something in your server’s response to help the browser identify the response as a Server Sent Event. When the server sends the data to the client, the Content-Type response header has to be set to text/event-stream. The content of the server’s response should be in the following format:

data: the data sent by server \n\n

data: marks the start of the data. \n\n marks the end of the stream.

Note: \n is the Carriage Return Character

You should note that the \n used above is the carriage return character, not simply a backslash and an n.

While this works for single-line updates, in most cases we’ll want our response to be multiline. In that case, the data should be formatted as follows:

data: This is the first line \n

data: Now it's the second line \n\n

After receiving this stream, client-side event.data will have both the lines separated by \n. You can remove these carriage returns from the stream as follows:

console.log(e.data.split('\n').join(' '));

How About a Little JSON?

In most real-world apps, sending a JSON response can be convenient. You can send the response this way:

data: {generator: "server", message: "Simple Test Message"}\n\n

Now in your JavaScript, you can access the data in the onmessage callback quite simply:

var parsedData = JSON.parse(event.data);

console.log(

"Received from " + parsedData.generator +

" and the message is: " + parsedData.message

);

Associating an Event ID

You can associate an event id with the data you are sending as follows:

id: 100\n

data: Hey, how are you doing?\n\n

Associating an event id can be beneficial because the browser tracks the last event fired. This id becomes a unique identifier for the event. In case of any dropped connection, when the browser reconnects to the server it will include an HTTP header Last-Event-Id in the request. On the server side, you can check the presence of the Last-Event-Id header. If it’s present, you can try to send only those events to the browser that have event id greater than the Last-Event-Id header value. In this way, the browser can consume the missed events.

Creating Your Own Events

One of the most crucial aspects of SSEs is being able to name your events. In a sports app, you can use a particular event name for sending score updates and another for sending other information related to the game. To do that, you have to register callbacks for each of those events on the client side. Here’s an example:

Response from server:

event: score \n

data: Some score!! \n\n

event: other \n

data: Some other game update\n\n

Client-side JavaScript:

var eventsource=new EventSource('source.php');

// our custom event

eventsource.addEventListener('score',function(e) {

// proceed with your logic

console.log(e.data);

}, false);

//another custom event

eventsource.addEventListener('other',function(e) {

// proceed in a different way

console.log(e.data);

}, false);

Having different event names allows your JavaScript code to handle each event in a separate way, and keeps your code clean and maintainable.

Handling Reconnection Timeout

The connection between the browser and server may drop any time. If that happens, the browser will automatically try to reconnect to the server by default after roughly five seconds and continue receiving updates; however, you can control this timeout. The following response from the server specifies how many milliseconds the browser should wait before attempting to reconnect to the server in case of disconnection:

retry: 15000 \n

data: The usual data \n\n

The retry value is in milliseconds and should be specified once when the first event is fired. You can set it to a larger value if your app does not produce new content rapidly and it’s okay if the browser reconnects after a longer interval. The benefit is that it may reduce the overhead of unnecessary HTTP requests.

Closing a Connection

We always reach this part in almost every API; when resources are no longer needed and it’s better to release them. So, how do you close an event stream? Write the following and you’ll no longer receive updates from the source:

eventsource.close(); //closes the event stream

A Sample Event Source

To finish with, how about some Chuck Norris jokes? Let’s create a simple EventSource that will randomly fetch a joke and push it to our HTML page. Here’s a simple PHP script that pushes the new data:

source.php

<?php

header('Content-Type: text/event-stream');

header('Cache-Control: no-cache');

ob_implicit_flush(true);

ob_end_flush();

while (true) {

sleep(2);

$curl=curl_init('http://api.icndb.com/jokes/random');

curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

$result=curl_exec($curl);

echo "data:$result\n\n";

}

?>

Note: Some Notes on the PHP Script

The data from the API is in JSON format. SSEs require you to prepend your data with data: and mark the end with \n\n. If you miss these points, the EventSource won’t work, and remember to also pay attention to the headers. The reason for the loop is to keep the connection open. Without the loop, the browser will attempt to open a new connection after five seconds or so.

As we are looping continuously in the PHP script, the execution time of the script may exceed PHP’s max_execution_time. You may also face problems because of the Apache user connection limit. Technologies such as Node.js may be a better choice for these types of real-time apps.

Here’s the HTML page that displays the data:

jokes.html

<!DOCTYPE html>

<html>

<head>

<title>A Random Jokes Website</title>

<script>

if(typeof(EventSource)!=="undefined"){

var eventsource=new EventSource('source.php');

eventsource.addEventListener('message', function(event) {

document.getElementById("container").innerHTML =

↵JSON.parse(event.data).value.joke + '<br/>' +

↵document.getElementById("container").innerHTML;

},false);

}

else console.log("No EventSource");

</script>

</head>

<body>

<div id="container"></div>

</body>

</html>

Debugging

In case you encounter problems, you can debug your application using the browser’s JavaScript console. In Chrome, the console is opened from Menu > Tools > JavaScript Console. In Firefox, the same can be accessed from Menu > Web Developer > Web Console. In case the event stream is not working as expected, the console may report the errors, if any.

You need to pay special attentions to the following:

· sending the header Content-Type: text/event-stream from the server

· marking the start and end of content with data: and \n\n respectively

· paying attention to same-origin policies

Conclusion

Server Sent Events have solved the long polling hack that we previously used to achieve real-time update functionality. Here are a few simple projects that you might want to implement:

· Creating a page that has a clock updated from the server side.

· Reading and displaying the latest tweets with the help of the Twitter API.

· Fetching a random photo with the Flickr API and updating the page.

The Mozilla Developer Network has a good resource for learning more about SSEs. Here, you can also find some demo apps and polyfills for older browsers.