Server Sent Events

Подготовил: Валерий Съестов Дата публикации: 09.01.2014
Последнее обновление: 27.01.2014

Server-sent events (SSE) - это технология, которая позволяет получать обновления веб - страницы с сервера в реальном режиме времени.

Таким образом, используя данную технологию, мы имеем возможность поддерживать постоянное обновление содержимого страницы.

Данная технология предоставляет много различных возможностей для html5 приложений, к примеру, если нужно постоянно обновлять информацию о ходе каких-то событий, выполнения длительных операций на стороне сервера, отображения информации о заказах, состоянии подключения к серверу, и так далее.

Server-Send Events vs. WebSockets

Существование такой технологии как WebSocket, которая предоставляет клиенту и серверу держать постоянное подключение, и работать в условиях приближенных к реальному времени, ставит под вопрос, в чем заключается преимущество и необходимость использования SSE технологии перед WebSockets?

Основная причина, почему SSE технология не столь известна как WebSockets, заключается в том, что ее появление было позже, чем WebSockets.

WebSockets - обеспечивает более богатый протокол для выполнения двунаправленной, полнодуплексной связи, и используется преимущественно для создания игр, чатов и всех тех приложений где скорость обмена между клиентом и сервером должна быть приведена максимально к режиму реального времени, а также в тех случаях, когда может понадобиться синхронизация между открытыми вкладками одного сайта, или одного сайта в разных браузерах.

Server-sent Events обеспечивает получение сообщений только со стороны сервера, и для работы этой технологии используется http протокол, в отличие от WebSockets.

Как это работает?

JavaScript API

Для начала работы необходимо определить источник получаемых сообщений. В качестве источника может выступать php скрипт.

var source = new EventSource("demo.php");

Далее для работы необходимо назначить обработчики событий. Объект EventSource поддерживает три стандартных типа событий, это:

Обработчик события Тип обработчика событий (addEventListener)
onopen open
onmessage message
onerror error

Так объект EventSource содержит такие свойства:

Наименование Значение
CONNECTING Числовое значение - 0. Соединение не было установлено, или было закрыто и восстанавливается
OPEN Числовое значение - 1. Соединение открыто и были привязаны события.
CLOSED Числовое значение - 2. Соединения закрыто и попытка переподключения не выполняется.

Кроме стандартных событий, можно организовать пользовательские события. Пользовательские события задаются определенной строкой ответа с сервера. Способы их определения будут описан ниже, при рассмотрении форматов сообщения которые формирует сервер.

Следующим шагом необходимо привязать события:

source.addEventListener("message", function(e) {
  console.log(e.data);
}, false);

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

source.addEventListener("error", function(e) {
  if (e.readyState === EventSource.CLOSED) {
    // Connection was closed.
  }
}, false);

Когда с сервера поступают новые сообщения, срабатывает событие onmessage. Данные которые были отправлены сервером, пишутся в атрибут события e.data.

В случае если связь с сервером разрывается, повторное подключение осуществляется примерно через 3 секунды. Интервал повторного подключения регулируется настройками сервера.

Формат ответа с сервера

Источник событий должен отвечать определенным стандартам для разграничения типа содержимого.

В нашем, demo.php для начала работы необходимо задать заголовок, который будет отдаваться с сервера:

header("Content-Type: text/event-stream"); // необходимо для работы Server-sent Events
header("Cache-Control: no-cache"); // рекомендуется делать для того, чтобы ответ не кэшировался.

Формат сообщения сервера должен содержать ключевое слово "data:", далее текст сообщения, и завершаться двумя символами "\n\n", пример:

data: Your message \n\n

В случае, если необходимо передать большое сообщение, тогда нужно в каждую строку с префиксом "data:" добавлять одни символ "\n", а последней строке два "\n\n".

data: first line\n
data: second line\n\n

В случае если необходимо отправить данные в формате JSON, можно использовать такой способ

data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n

source.addEventListener("message", function(e) {
  var data = JSON.parse(e.data);
  console.log(data.id, data.msg);
}, false);

В случае если необходимо сервером отправлять уникальные события, со стороны сервера в ответе с помощью ключевого слова "event:" задается название события.

event: eventName\n
data: {"name": "value"}\n\n

source.addEventListener("message", function(e) {
  var data = JSON.parse(e.data);
  console.log(data.msg);
}, false);

source.addEventListener("eventName", function(e) {
  var data = JSON.parse(e.data);
  console.log("User login:" + data.username);
}, false);

Пример:

serverEvents: (function () {
            "use strict";

            var controls = {},
                handlers = {
                    open: function (event) {
                        console.log(event);
                    },

                    message: function (event) {
                        console.log(event);
                    },

                    error: function (event) {
                        console.log(event);
                    },

                    getId: function (event) {
                        console.log("id");
                    }
                };

            function getControls() {
                controls.eventSource = new EventSource("php/server-events.php");
            }

            function addEvent() {
                var eventSource = controls.eventSource;
                eventSource.addEventListener("open", handlers.open, false);
                eventSource.addEventListener("message", handlers.message, false);
                eventSource.addEventListener("get_id", handlers.getId, false);
                eventSource.addEventListener("error", handlers.error, false);
            }

            function init() {
                getControls();
                addEvent();
            }

            return {
                init: function () {
                    init();
                }
            }
        }())

Data.php

header("Content-Type: text/event-stream");
header("Content-Encoding: none; ");
header("Cache-Control: no-cache");
header("Access-Control-Allow-Origin: *");

$lastEventId = floatval(isset($_SERVER["HTTP_LAST_EVENT_ID"]) ? $_SERVER["HTTP_LAST_EVENT_ID"] : 0);
if ($lastEventId == 0) {
    $lastEventId = floatval(isset($_GET["lastEventId"]) ? $_GET["lastEventId"] : 0);
}

ob_start();

echo ":" . str_repeat(" ", 2048) . "\n"; // 2 kB padding for IE
echo "retry: 2000\n";

// event-stream
$i = $lastEventId;
$c = $i + 100;

while (++$i < $c) {
    echo "id: " . $i . PHP_EOL;
    echo "event: get_id" . PHP_EOL;
    echo "data: " . $i . ";" . PHP_EOL;
    echo PHP_EOL . str_repeat(" ", 1024 * 64); // Заполняется ответ пробелами, необходимо для корректного вывода буфера

    ob_flush();
    flush();
    sleep(1);
}

Так как данная технология на текущий момент не поддерживается в IE, можно воспользоваться библиотекой https://github.com/Yaffle/EventSource/, которая эмулирует работу объекта EventSources. Для работы библиотеки, ее нужно просто подключить.

Пример в действии:

http://jsfiddle.net/valsie/MCLds/1/

Показать комментарии