Using server-sent events

Разрабатывать web-приложения, использующие Server-Sent Events намного проще, чем с использованием websockets. Нужно всего лишь немного кода на стороне сервере, чтобы переправлять события web-приложению, но клиентская часть кода для обработки этих событий работает почти точно так же, как и для любых других событий.

Получение событий от сервера

Server-Sent Event API содержится внутри интерфейса EventSource. Чтобы открыть соединение с сервером для начала записи событий, которые он присылает, необходимо создать новый объект EventSource, который будет указывать на URI скрипта, который создает события. Например:

const evtSource = new EventSource("ssedemo.php");

Если файл с генератором событий размещен на другом домене, то должен быть создан новый объект EventSource в который следует передать помимо URI еще и словарь опций. Например, если предположить, что клиентский скрипт находится на example.com:

const evtSource = new EventSource("//api.example.com/ssedemo.php", { withCredentials: true } ); 

Как только вы создали экземпляр EventSource, вы можете начать прослушивание сообщений с сервера, добавив обработчик для события message :

evtSource.onmessage = function(event) {
  const newElement = document.createElement("li");
  const eventList = document.getElementById('list');

  newElement.innerHTML = "message: " + event.data;
  eventList.appendChild(newElement);
}

Этот код прослушивает входящие сообщения (то есть уведомления от сервера, на которых нет поля event) и добавляет текст сообщения в список в HTML-документе.

Вы также можете прослушивать события, используя addEventListener():

evtSource.addEventListener("ping", function(event) {
  const newElement = document.createElement("li");
  const time = JSON.parse(event.data).time;

  newElement.innerHTML = "ping at " + time;
  eventList.appendChild(newElement);
});

Этот код аналогичен коду выше, за исключением того, что он будет вызываться автоматически всякий раз, когда сервер отправляет сообщение с полем event, установленным в «ping»; затем он парсит JSON в поле data и выводит эту информацию.

Отправка событий с сервера

Код на стороне сервера, который отправляет события, должен отвечать, используя MIME-тип text/event-stream. Каждое уведомление отправляется в виде блока текста, оканчивающегося парой новых строк (\n) . Подробнее о формате потока событий см.  Event stream format.

PHP код, который мы используем для примера приведен ниже:

date_default_timezone_set("America/New_York");
header('Cache-Control: no-cache');
header("Content-Type: text/event-stream\n\n");

$counter = rand(1, 10);
while (1) {
  // Every second, send a "ping" event.
  
  echo "event: ping\n";
  $curDate = date(DATE_ISO8601);
  echo 'data: {"time": "' . $curDate . '"}';
  echo "\n\n";
  
  // Send a simple message at random intervals.
  
  $counter--;
  
  if (!$counter) {
    echo 'data: This is a message at time ' . $curDate . "\n\n";
    $counter = rand(1, 10);
  }
  
  ob_end_flush();
  flush();
  sleep(1);
}

Приведенный выше код генерирует событие каждую секунду с типом события «ping». Данные каждого события - это объект JSON, содержащий метку времени ISO 8601, соответствующую дате, когда было сгенерировано событие. Через случайные интервалы отправляется простое сообщение (без типа event).

Примечание: Вы можете найти полный пример, который использует код, показанный в этой статье на GitHub - см. Simple SSE demo using PHP.

Обработка ошибок

Когда возникают проблемы (такие как тайм-аут ответа сети или проблемы, связанные с контролем доступа), тогда генерируется событие error. Вы можете обработать это событие программно, реализовав метод onerror для объекта EventSource:

evtSource.onerror = function(error) {
  console.error("⛔ EventSource failed: ", error);
};

Закрытие потоков событий

По умолчанию, если соединение между клиентом и сервером закрывается, то соединение сбрасывается. Прервать соединение можно с помощью метода .close().

evtSource.close();

Формат потока событий

Поток событий представляет из себя простой поток текста, который должен иметь кодировку UTF-8. Сообщения в потоке событий разделяются парой символов новой строки. Двоеточие, как первый символ строки считается комментарием и игнорируется.

Примечание: Строка комментария может использоваться, чтобы предотвратить тайм-аут соединений; сервер может периодически отправлять комментарий, чтобы поддерживать соединение.

Каждое сообщение содержит одну или более строчек текста, которые перечиляют поля этого сообщения. Каждое имеет свое имя, за которым следует двоеточие, после которого идут текстовые данные для значения этого поля. 

Поля

Каждое полученное сообщение имеет некоторую комбинацию следующих полей, по одному на строку:

event
Строка, идентифицирующая тип описанного события. Если event указан, то событие будет отправлено в браузер слушателю для указанного имени события. Исходный код сайта должен использовать addEventListener() для прослушивания именованных событий. Обработчик onmessage вызывается, если для сообщения не указано имя события.
data
Поле данных для сообщения. Когда EventSource получает несколько последовательных строк, начинающихся с data:, он объединяет их, вставляя символ новой строки между каждой из них. Последние переводы строки удаляются.
id
Идентификатор события для установки значения последнего ID события для объекта EventSource.
retry
Время переподключения, используемое при попытке отправить событие. Это должно быть целое число, указывающее время переподключения в миллисекундах. Если указано нецелое значение, поле игнорируется.

Другия названия полей игнорируются.

Примечание: If a line doesn't contain a colon, the entire line is treated as the field name with an empty value string.

Примеры

Сообщения с данными

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

Второе сообщение содержит поле данных со значением «some text». Третье сообщение содержит поле данных со значением «another message \n with two lines». Обратите внимание на специальный символ новой строки в значении.

: this is a test stream

data: some text

data: another message
data: 

Именованные события

Данный пример отправляет именованные события. У каждого из них есть имя события, указанное в поле event, и поле data, значением которого является соответствующая строка JSON с данными, необходимыми для клиента, чтобы реагировать на событие. Поле data может, конечно, содержать любые строковые данные; это не обязательно должен быть JSON.

event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}

event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}

Смешивание и сопоставление

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

event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

data: Here's a system message of some kind that will get used
data: to accomplish some task.

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

Совместимость с браузерами

EventSource

Update compatibility data on GitHub
КомпьютерыМобильные
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome для AndroidFirefox для AndroidOpera для AndroidSafari on iOSSamsung Internet
EventSourceChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка 11Safari Полная поддержка 5WebView Android Полная поддержка ≤37Chrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 11Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
EventSource() constructorChrome Полная поддержка 9Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка 11Safari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
closeChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
error eventChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
message eventChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
onerrorChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
onmessageChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
onopenChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
open eventChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
readyStateChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
urlChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
withCredentialsChrome Полная поддержка 6Edge Полная поддержка 79Firefox Полная поддержка 6IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка 5WebView Android Полная поддержка ДаChrome Android Полная поддержка 18Firefox Android Полная поддержка 45Opera Android Полная поддержка 12Safari iOS Полная поддержка 5Samsung Internet Android Полная поддержка 1.0
Available in workersChrome Полная поддержка ДаEdge Полная поддержка 79Firefox Полная поддержка 53IE Нет поддержки НетOpera Полная поддержка ДаSafari Полная поддержка ДаWebView Android Полная поддержка ДаChrome Android Полная поддержка ДаFirefox Android Полная поддержка 53Opera Android Полная поддержка ДаSafari iOS Полная поддержка ДаSamsung Internet Android Полная поддержка Да

Легенда

Полная поддержка  
Полная поддержка
Нет поддержки  
Нет поддержки