Dealing with Server-sent Events in Html5

Imagine you’re building a simple stock ticker application. You have a server resource that publishes the APIs you need for getting stock values, so it’s quite easy to get things started. But how do you get updates to the stock values? How does the server let your client application know that a stock’s value has changed?

This is probably the canonical use case for Server-sent Events: a situation in which the server needs to inform the client that something has happened. Because HTTP as a standard only defines stateless communication, and thus only clients can initiate requests to servers, there was no way for a server to send a message to a client without the client first asking for one. Server-sent Events is one of the ways that HTML5 addresses this issue, in the specific case of one-way communication from the server to the client.

<!DOCTYPE HTML>
<html>
<head>
<title>The HTML5 Programmer’s Reference</title>
</head>
<body>
<script>

// This is a mock API that simply returns the same JSON structure
// every time the URL is requested. This JSON structure has a single
// property, isChanged, which is set to false.
var strUri = ’./example3-1.json’;

// This is how often we’ll query the mock API, in milliseconds.
var timerLength = 1000;

/**
* Fetch an update from a web service at the specified URL. Initiates an
* XMLHttpRequest to the service and attaches an event handler for the success
* state.
* @param {string} strUrl The URL of the target web service.
*/
function fetchUpdates(strUrl) {
// Create and configure a new XMLHttpRequest object.
var myXhr = new XMLHttpRequest();
myXhr.open("GET", strUrl);
// Register an event handler for the readyStateChange event published by
// XMLHttpRequest.
myXhr.onreadystatechange = function() {
// If readyState is 4, request is complete.
if (myXhr.readyState === 4) {
handleUpdates(myXhr.responseText);
}
};
// Everything is configured. Send the request.
myXhr.send();
}

/**
* Handle an update from the mock API. Parses the JSON returned by the API and
* looks for changes.
* @param {string} jsonString A JSON-formatted string.
*/
function handleUpdates(jsonString) {
// Parse the JSON string.
var jsonResponse = JSON.parse(jsonString);
if (jsonResponse.isChanged) {
// Handle changes here, probably by checking the structure to determine
// what changed and then route the change to the approprate part of the app.
console.log(’change reported.’);
} else {
console.log(’no changes reported.’);
}
}

// Everything is all set up: we have a function for fetching an update and a
// function to handle the results of an update query. Now we just have to kick
// off everything. Using setInterval, we will call our fetchUpdates method every
// timerLength milliseconds. We can cancel the timer by calling the
// cancelInterval(pollTimer) method.
var pollTimer = setInterval(fetchUpdates.bind(this, strUri), timerLength);
</script>
</body>
</html>

This example does a lot of setup before it starts the polling process. Begin by defining the URL for the mock API, which is just a file you have created on the filesystem. You also define how often you want to poll your mock API. Then create a function for fetching an update. This is the method you will call with the timer, and it initiates the XMLHttpRequest (XHR for short) to the mock service. The XHR will publish readyStateChange events as it communicates with the server. By looking at the readyState property of the XHR object, you can tell what state the query is in: still talking with the server, or done talking, or even if an error has occurred. So add an event handler to the XHR object that will be called each time a readyStateChange event occurs. In this case you’re using an inline function to keep the code simple, though you could have defined it outside of this code block and referred to it by name. The event handler checks the readyState property and, if it is in the correct state, it calls the handleResponse function. That function takes the JSON-formatted string that you fetched from your mock API and processes it accordingly.

This is a pretty unsophisticated example, but it demonstrates the basics of using a timer to regularly poll a web service. Using the methods setInterval and cancelInterval it’s easy to start and stop timers. If your application will need multiple timers, you can build a constructor with convenience methods for creating, starting, stopping, pausing, and disposing of them. You can rapidly build up a lot of code just around creating and managing your timers.

And if you think about it, simple timers aren’t really a good way of handling this problem. What if there is a temporary network problem and one of the calls to fetchUpdates takes longer than a second? In that case, another call to fetchUpdates would be executed before the first has returned, resulting in two active and pending calls to the server. Depending on the network conditions, the second call could return before the first. This situation is referred to as a race condition, because the two pending calls are essentially in a race to see which one completes first.

Fortunately this race condition is fairly easy to fix: instead of having a timer fire off requests regardless of external limitations, alter the handleUpdates method so that the last thing it does is schedule the next call to fetchUpdates. That way you won’t ever have more than one call happening and the race condition is eliminated.

// This is a mock API that simply returns the same JSON structure
// every time the URL is requested. This JSON structure has a single
// property, isChanged, which is set to false.
var strUri = ’./example3-1.json’;

// This is how often we’ll query the mock API, in milliseconds.
var timerLength = 1000;

// Reference to the currently active timer.
var pollTimeout;

/**
* Fetch an update from a web service at the specified URL. Initiates an
* XMLHttpRequest to the service and attaches an event handler for the success
* state.
* @param {string} strUrl The URL of the target web service.
*/
function fetchUpdates(strUrl) {
// Create and configure a new XMLHttpRequest object.
var myXhr = new XMLHttpRequest();
myXhr.open("GET", strUrl);
// Register an event handler for the readyStateChange event published by
// XMLHttpRequest.
myXhr.onreadystatechange = function() {
// If readyState is 4, request is complete.
if (myXhr.readyState === 4) {
handleUpdates(myXhr.responseText);
}
};
// Everything is configured. Send the request.
myXhr.send();
}

/**
* Handle an update from the mock API. Parses the JSON returned by the API and
* looks for changes, and then initiates the next query to the mock service.
* @param {string} jsonString A JSON-formatted string.
*/
function handleUpdates(jsonString) {
// Parse the JSON string.
var jsonResponse = JSON.parse(jsonString);
if (jsonResponse.isChanged) {
// Handle changes here, probably by checking the structure to determine
// what changed and then route the change to the approprate part of the app.
console.log(’change reported.’);
} else {
console.log(’no changes reported.’);
}
// Schedule the next polling call.
pollTimeout = setTimeout(fetchUpdates.bind(this, strUri), timerLength);
}

// Initiate polling process.
fetchUpdates(strUri);

The changes from the previous version of the script have been bolded. This version of the code eliminates the race condition, because only one call to fetchUpdates will be active at any given time. However, you could now be polling the server at an unpredictable rate.

Recommended :  Uploading an image using Redactor in laravel

It is possible to program around these problems as well, but handling all of the edge cases well can be difficult, and you will end up with a significant amount of code, all just to intelligently handle polling a web service.

Ideally, this sort of communication should be a feature of the browser, and that’s what the new Server-sent Events feature does. Server-sent Events has the browser handle all of the details of connecting to the server and polling it for events, and lets you leave behind timer-based polling scripts and all of the problems they entail. Server-sent Events provide a way for you to open a channel to the server, and then attach event listeners to that channel to handle various event types that the server will publish. The browser handles everything else for you.

To use Server-sent Events, you need not only a client that can open a channel to a web service and handle the events that occur on that channel; you also need a server that will handle incoming channel subscriptions correctly and publish correctly formatted events.

Client Setup

The Server-sent Events specification defines a new constructor, EventSource, in the global context, which you can use to create new connections to the server:

var serverConnection = new EventSource(targetUrl);

The constructor returns an EventSource object that is an interface to a connection that the browser will maintain to the server resource specified by targetUrl. The browser will handle all of the connection maintenance and polling for you—all you have to do is listen for events from the server.

As the server publishes events to the connection, the server resource will publish events that will be dispatched from the EventSource object. Like any DOM event, you can attach event handlers to the EventSource object using the addEventListener method.

By default, the EventSource interface will publish three event types:

  • open: Published when the connection is first opened and network communication has been initialized. Useful for initializing the connection. Fires at most once, and if the browser fails to establish a connection to the specified service, it won’t fire at all.
  • message: Published when the server sends a new message.
  • error: Published if an error occurs in the connection (e.g., the connection times out).

When an event is dispatched, the EventSource will call the event handling function registered for that event type. The function will be called with an event object as a parameter, and that event object will have a data attribute that will contain the data that was sent from the server. Stubbed EventSource Event Handlers and Subscriptions

/**
* Handles message events published by the EventSource.
* @param {EventSourceEvent} event
*/
function handleMessage(event) {
// Handle message.
console.log(’A message was sent from the server: ’, event.data);
}

/**
* Handles error events published by the EventSource.
* @param {EventSourceEvent} event
*/
function handleError(event) {
// Handle an error.
console.error(’An error happened on the EventSource: ’, event.data);
}

/**
* Handles an open event published by the EventSource.
* @param {EventSourceEvent} event
*/
Function handleOpen(event) {
// Handle the open event.
console.log(’The connection is now open.’);
}

// Create a new connection to the server.
var serverConnection = new EventSource(targetUrl);

// Attach event handlers.
serverConnection.addEventListener(’message’, handleMessage);
serverConnection.addEventListener(’error’, handleError);
serverConnection.addEventListener(’open’, handleOpen);

Now whenever the resource specified by strUrl publishes an event, the handleMessage event handler will be called. If an error arises in the connection the browser will publish an error event and the handleError event handler will be called. Note that you can configure your server to publish custom event types, and you can add event handlers for them in exactly the same way (see next section, “Sending Events from the Server”).

To close the connection to the server, call the close method on the EventSource object:

serverConnection.close();

This will signal the browser to stop polling the server and close the connection. You can then set the EventSource object to null to eliminate it from memory. There is no way to reopen a connection once it has been closed.

Recommended :  Using pretty URLs in yii framework

Sending Events from the Server

For Server-sent Events to work, you need a resource on a server that knows how to handle the incoming polling requests from the browser and how to correctly publish events as needed. The server resource can be written in any language—Java, PHP, JavaScript, C++, and so forth. The resource must respond to polling requests with the text/event-stream MIME type. Responses consist of multiline key: value pairs, and are terminated by a double linefeed. Valid keys are as follows:

  • data: This specifies a line of arbitrary data to be sent to the client, which will receive it as the data property of the event object.
  • event: Specifies an arbitrary event type associated with this Server-sent Event. This will cause an event of the same name to be dispatched from the active EventSource object, thus enabling arbitrary events beyond open, message, and error to be fired from the server. If no event type is specified, the event will just trigger a message event on the EventSource.
  • id: This specifies an arbitrary ID to associate with the event sequence. Setting an ID on an event stream enables the browser to keep track of the last event fired, and if the connection is dropped it will send a last-event-ID HTTP header to the server.
  • retry: Specifies the number of milliseconds before the browser should re-query the server for the next event. By default this is set to 3000 (three seconds). This enables the server resource to throttle browser queries and prevent itself from being swamped.

For example, a basic Server-sent Event would look like this:

data: arbitrary line of text\n\n

This event would trigger a message event on the associated EventSource object and call the message event handler (assuming one was registered). The event object received by the message event handler will have a data attribute, which will contain the text sent by the server (in the preceding case, it would be “arbitrary line of text”).

You can send multiple line events as well—just terminate the event with a double-linefeed:

data: arbitrary line of text\n
data: another arbitrary line of text\n\n

In this case, the event.data attribute would be set to “arbitrary line of text\nanother arbitrary line of text”. The event data can be any text: HTML, CSS, XML, JSON, and so forth.

Multiple event types can be included in a single Server-sent Event as well. Going back to the original example of a stock ticker, here is an event that shows updates on two different stocks:

event: update\n
data: {\n
data: "ticker":"GOOG",\n
data: "newval":"559.89",\n
data: "change":"+0.05"\n
data: }\n
event: update\n
data: {\n
data: "ticker":"GOOGL"\n
data: "newval":"571.65"\n
data: "change":"+1.09"\n
data: }\n\n

This single Server-sent Event would trigger two update events on the EventSource object. Each time the update event handler would be called, with an event object containing the data for that event. The data for the first event would be the following JSON-formatted text:

{
"ticker":"GOOG",
"newval":"559.89",
"change":"+0.05"
}

And the data for the second event would be the following JSON-formatted text:

{
"ticker":"GOOGL",
"newval":"571.65",
"change":"+1.09"
}

Origin Limitations

Server-sent Events are subject to the Same Origin Policy, so a page from one origin cannot subscribe to an event stream from another. In particular, event streams are often published on different TCP ports than standard web pages, so it’s not possible for a web page published on a standard port like port 80 to subscribe to an event stream published on a different port, even if it is from the same server. Only clients served from the same origin as the event stream can access that event stream.

If you want to use Server-sent Events, you will need to have a web server that is flexible enough to serve the HTML-based client (and all of its dependent resources like CSS, JavaScript, images, etc.) and publish event streams. This makes server-integrated scripting languages like PHP, JSP, or ColdFusion prime candidates for building application servers that rely on Server-sent Events, because you can write the event streams in the integrated scripting language and serve the clients using the same web server. It’s also quite easy to configure most web servers to route requests to special URLs to different resources, making it possible to publish both regular web content and event streams from the same server. The details of such implementations are beyond the scope of this book, but this is an important limitation to Server-sent Events.

In the example that follows, you’ll opt for a simpler solution: building a server that can serve the client HTML file while acting as an event stream. Since both the HTML client file and the event stream are from the same origin, there will be no problems with the subscription.

Security

Just as with any technique for handling network communication, it’s a good idea to be conscious of security when you’re building applications with Server-sent Events. Never send sensitive information (e.g., passwords) on unencrypted connections, because server events are sent in plain text. If you need to send sensitive information you should at the very least use HTTPS.

As mentioned, Server-sent Events are limited by the Same Origin Policy, so a script cannot subscribe to an event stream from a network resource different than its own. In addition, the event object received by EventSource event handlers will have an origin property that you can check to verify that the server event is coming from the source you expect Checking Event Origins from Server-sent Events

// The EventSource object.
var serverConnection;

/**
* Handle an event published on an EventSource object.
* @param {EventSourceEvent} event
*/
function messageHandler(event) {
if (event.origin !== ’https://www.myexample.com’) {
// Something bad has happened, stop listening for events and surface a warning to the user.
serverConnection.close();
alert(’Warning: Server event received from wrong network resource.’);
return;
}
// Handle event here.
}

// Initiate subscription to event stream and register event handler.
serverConnection = new EventSource(targetUrl);
serverConnection.addEventListener(’message’, messageHandler);

In this snippet you’re checking the origin of the event as reported by the browser. This is not a foolproof check, however, as it can be spoofed, but it’s one more layer of security you can add to your application.

Recommended :  what is Auto Hydrate in laravel

An Example Application

To build a functional example application, you’ll need a server resource that can send the events in the expected format and can also serve the client that will subscribe to the event stream. As mentioned, you could use any language to build this server resource, but to stay consistent with the other examples in the book, use JavaScript. You’re probably used to using JavaScript in the browser. You can also use it on a server, just like any other scripting engine. The most popular implementation of JavaScript as a standalone scripting engine is the Node.js framework, which has been ported to multiple operating systems. The Node.js framework provides a fast JavaScript interpreter and a framework of APIs for accessing the filesystem, network stack, and other resources on the server.

ImageTip  If you’ve never used Node.js, you can learn more about it at http://nodejs.org.

To run this example, you’ll need a server with Node.js installed. You’ll build a simple script that will both act as the event streamer and serve the event client.

A Simple Event Stream Server Written in JavaScript

// Include the modules needed to build the server.
var http = require(’http’);
var sys = require(’sys’);
var fs = require(’fs’);

// Use the http.createServer method to create a server listening on port 8030.
// The server will call the handleRequest method each time a request is
// received.
http.createServer(handleRequest).listen(8030);

/**
* Handle an incoming request from the server.
* @param {Object} request The request headers.
* @param {Object} resource A reference to the server resource that received
* the request.
*/
function handleRequest(request, resource) {
// Incoming requests to our server will be to one of two URLs.
// If the request is for /example3-5-events we should send our SSE.
// If the request is for /example3-5.html, we should serve the example client.
if (request.url == ’/example3-5-events’) {
// Initialize an event stream.
sendSSE(request, resource);
} else if (request.url == ’/example3-6.html’){
// Send the client.
resource.writeHead(200, {’Content-Type’: ’text/html’});
resource.write(fs.readFileSync(’example3-6.html’));
resource.end();
}
}

/**
* Initializes an event stream and starts sending an event every 5 seconds.
* @param {Object} request
* @param {Object} resource
*/
function sendSSE(request, resource) {
// Initialize the event stream.
resource.writeHead(200, {
’Content-Type’: ’text/event-stream’,
’Cache-Control’: ’no-cache’,
’Connection’: ’keep-alive’
});

// Send an event every 5 seconds.
setInterval(function() {
// Randomly generate either 0 or 1.
var randNumber = Math.floor(Math.random() * 2);
// If the random number is 1, set isChanged to true. Otherwise, set it to
// false.
var isChanged = (randNumber === 1) ? true : false;
resource.write(’data: ’ + ’{"isChanged":’ + isChanged + ’}\n\n’);
}, 5000);
}

If you request example3-6.html it will serve the HTML client, and if you request example3-5-events it will initiate an event stream that will push an event to the client every five seconds. The event will be a simple JSON-formatted string with an isChanged property that will be set randomly to true or false. To run this server, use the following command:

node example3-5server.js

The HTML client for this server just has to initiate the EventSource to the correct URL,

 A Server-sent Event Client

<!DOCTYPE HTML>
<html>
<head>
<title>The HTML5 Programmer’s Reference</title>
<style>
#changeme {
width: 300px;
height: 300px;
border: 1px solid black;
overflow: auto;
}
</style>
</head>
<body>
<h1>Server-sent Events Demonstration</h1>
<div id="changeme"></div>
<script>
// The URL for our event stream. Note that we are not specifying a domain or
// port, so they will default to the same ones used by the host script.
var strUri = ’/example3-5-events’;
// Get a reference to the DOM element we want to update.
var changeMe = document.getElementById(’changeme’);

// Create a new server-side event connection and register an event handler for
// the ’message’ event.
var serverConnection = new EventSource(strUri);
serverConnection.addEventListener(’message’, handleSSE, false);

/**
* Handles a server-sent event by parsing the JSON in the data and handling
* any changes.
* @param {EventSourceEvent} event The event object from the event source.
*/
function handleSSE(event) {
// Parse the JSON string.
var jsonResponse = JSON.parse(event.data);
// Create a new element to append to the DOM.
var newEl = document.createElement(’div’);
if (jsonResponse.isChanged) {
newEl.innerHTML = ’Change reported.’;
} else {
newEl.innerHTML = ’No changes reported.’;
}
// Append the new element to the DOM.
changeMe.appendChild(newEl);
}
</script>
</body>
</html>

This client initiates a new EventSource for the event stream’s URL and then attaches a message event handler to it. Every time the server publishes an event, the message event handler is called, the event data is parsed, and the results are appended to the DOM. This client is a lot simpler than your previous polling example because all of the details are handled by the browser now. There’s no need to initiate an XMLHttpRequest object, no need to manage your own timers—all you have to do is initialize an EventSource object and register event handlers.

About the author

Deven Rathore

I'm Deven Rathore, a multidisciplinary & self-taught designer with 3 years of experience. I'm passionate about technology, music, coffee, traveling and everything visually stimulating. Constantly learning and experiencing new things.

Pin It on Pinterest

Shares