Sunday, December 21, 2014

Introduction to Server-Sent Events with PHP example

http://www.howopensource.com/2014/12/introduction-to-server-sent-events

Server-sent events (SSE) is a web technology where a browser receives automatic updates from a server via HTTP protocol. SSE was known before as EventSource and first is introduced in 2006 by Opera. During 2009 W3C started to work on first draft. And here is a latest W3C proposed recommendation from December 2014. It is little know feature that was implemented by all major web browsers except Internet Explorer and may be this is the reason why it is not widely known and used. The idea behind Server-sent events is very simple – a web application subscribes to a stream of updates generated by a server and, whenever a new event occurs, a notification is sent to the client.
But to really understand power of Server-Sent Events, we need to understand the limitations of AJAX version. First was Polling – polling is a technique used by majority of AJAX applications. Idea is that the JavaScript via AJAX repeatedly polls a server for a new data in a given interval (5 seconds for example). If there is new data, server returns it. If there is no new data, server simply return nothing. The problem with this technique is that creates additional overhead. Each time connection needs to be open and then closed.
Next method that was introduces was Long polling (aka COMET). Difference between polling and long polling is that when request is made and there is no data – server simply hangs until new data comes. Then server returns data and closes connection. This was also know as hanging GET method. So instead to returns empty response server waits until data comes, then returns data and closes HTTP connection.
Next comes WebSocket which is bi-directional rich media protocol and can be used in a lot of cases. But WebSocket needs a different protocol, different server side code and it is a little bit complicated compared to SSE.
So what is good for Server-sent events? It could be used in cases when data communicates in one way – from server to client. Here are couple of cases which SSE is very useful: real-time stock prices update; live score and sports events; server monitoring web applications.
Benefit of using Server-sent events instead of AJAX polling or long polling is that technology is directly supported by major web browsers; protocol that is used is HTTP so it is very easy to implement it on server side as well. SSE does not generate overhead and everything that you need is handled by web browser. Here is current state of SSE support.

Protocol description

Data are sent in plain text. So SSE is not suitable for binary data, but it is perfect for text events. First step is to set correct response header Content-Type to text/event-stream.
header("Content-Type: text/event-stream");
Next step is to construct and send data. Basically response contains keyword data followed by data you want to send and two new lines.
data: Hello World!\n\n
If you want to send multiple lines you can separate them by one new line.
data: Hello World!\n
data: This is a second line!\n\n
Here is how to send JSON data:
data: {"msg": "Hello World!"}\n\n
Or to split JSON in multiple lines:
data: {\n
data: "msg": "Hello World!",\n
data: "line2": "This is a second line!"\n
data: }\n\n
Here is PHP code that does the above:
header("Content-Type: text/event-stream");
echo "data: Hello World!\n\n";
This is one event with in multiple lines. Note that new line character is a separator. if you need to send new line character consider to escape it or to properly split message in multiple lines:
header("Content-Type: text/event-stream");
echo "data: Hello World!\n";
echo "data: This is a second line!\n\n";
Here is how to send to events with some delay:
header("Content-Type: text/event-stream");
echo "data: First message\n\n";

echo "data: Second message\n\n";
But what happens when connection is lost or closed – well browser opens again connection after 3 seconds. To control re-connection time you should use a keyword retry with first message. Number passed after retry is in milliseconds. Here is example – tell browser to reconnect after 2 seconds if HTTP connection is lost.
header("Content-Type: text/event-stream");
echo "retry: 2000\n";
echo "data: Hello World!\n\n";
When browser reconnects meanwhile some event happens how do you know which was sent and which was not – well you can associate unique id with each event. When reconnects browser send HTTP header Last-Event-ID. Based on that header you know which is the last event browser received.
id: 1\n
data: Hello World!\n\n

id: 2\n
data: Second message\n\n

JavaScript API

Using EventSource in browser is simple and easy. First you check that browser supports EventSource API then you create event source by passing URL to which to listen.
if (!!window.EventSource) {
    var source = new EventSource("data.php");
} else {
    alert("Your browser does not support Server-sent events! Please upgrade it!");
}
EventSource object has three listeners to subscribe. Most important is message, others are open and error.
source.addEventListener("message", function(e) {
    console.log(e.data);
}, false);

source.addEventListener("open", function(e) {
    console.log("Connection was opened.");
}, false);

source.addEventListener("error", function(e) {
    console.log("Error - connection was lost.");
}, false);
Most important properties of Event object passed to listener functions are data and lastEventId.
One interesting feature is named events. You can specify name of different events and on client side different listeners to be fired based on that events.
event: priceUp\n
data: GOOG:540\n\n
Then on client side you can subscribe to this event by passing it to listener function:
source.addEventListener("priceUp", function(e) {
    console.log("Price UP - " + e.data);
}, false);

PHP Server Code

Only change in PHP code (or other server side code) is that you need of infinite loop to keep connection open. Here is some code from example:
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Connection: keep-alive");

$lastId = $_SERVER["HTTP_LAST_EVENT_ID"];
if (isset($lastId) && !empty($lastId) && is_numeric($lastId)) {
    $lastId = intval($lastId);
    $lastId++;
}

while (true) {
    $data = \\ query DB or any other source - consider $lastId to avoid sending same data twice
    if ($data) {
        sendMessage($lastId, $data);
        $lastId++;
    }
    sleep(2);
}

function sendMessage($id, $data) {
    echo "id: $id\n";
    echo "data: $data\n\n";
    ob_flush();
    flush();
}

Show me example

After so many words and code snippets I put all together and created simple Stock Tickets web application that updates price of some selected stocks. Data source is not real but it is simple multidimensional array with very simple structure – ticked – price. Otherwise everything is real and could be used to explore and study the code.

No comments:

Post a Comment