Xiper

Server Sent Events

Автор: Валерий Съестов Дата публикации:

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:", далее текст сообщения, и завершаться двумя символами "nn", пример:

data: Your message nn

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

data: first linen
data: second linenn

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

data: {n
data: "msg": "hello world",n
data: "id": 12345n
data: }nn
source.addEventListener("message", function(e) {
  var data = JSON.parse(e.data);
  console.log(data.id, data.msg);
}, false);

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

event: eventNamen
data: {"name": "value"}nn
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: 2000n";
// 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, можно воспользоваться библиотекой, которая эмулирует работу объекта EventSources. Для работы библиотеки, ее нужно просто подключить.

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