Будьте в курсе последних событий, подпишитесь на обновления сайта

Server Side Events — события на стороне сервера

Server Side Events — события на стороне сервера

Здравствуйте! В этом уроке я расскажу о такой технологии, как Server Side Events, которая позволяет  браузеру общаться с сервером.  Сразу  хочу заметить, что на сегодняшний день этот способ поддерживают все современные браузеры, кроме конечно же  IE и Edge.

Стандарт Server-Sent Events позволяет браузеру создавать объект EventSource, который сам устанавливает соединение с сервером, делает пересоединение в случае потери соединения и генерирует различные  события при поступлении данных.

Он  может  даже меньше, чем WebSocket’ы.

С другой стороны, Server Side Events проще в реализации, работают по протоколу HTTP и сразу поддерживают ряд возможностей, которые для WebSocket ещё надо делать.

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

server side events

Получение сообщений

При создании объекта new EventSource(src) браузер автоматически подключится к адресу src и начннет получать с него события:

var eventSource = new EventSource("/events/subscribe"); 
eventSource.onmessage = function(e) { 
console.log("Пришло сообщение: " + e.data); };

Чтобы соединение успешно открылось, сервер должен ответить заголовком Content-Type: text/event-stream, а затем не закрывать соединение и писать в него сообщения в специальном формате:

data: Сообщение 1
data: Сообщение 2
data: Сообщение 3
data: из двух строк
  • Каждое сообщение пишется только после data:. Если после двоеточия будет пробел, то он игнорируется.
  • Сообщения разделяются 2 строками \n\n.
  • Если нужно переслать перевод строки, то сообщение разделяется. Каждая следующая строка пересылается отдельным data:.В частности, две последние строки в примере выше составляют одно сообщение: «Сообщение 3\nиз двух строк».

Здесь все очень просто и удобно, кроме разделения сообщения при переводе строки. Но, если подумать – это не так уж страшно: на практике сложные сообщения обычно передаются в формате JSON. А перевод строки в нём кодируется как \n.

Соответственно, многострочные данные могут пересылаться так:

data: {"user":"Вася","message":"Сообщение 3\n из двух строк"}

Таким образом, строка data: будет одна, и никаких проблем с разделением сообщения не будет.

Читайте также  Введение в AJAX

Восстановление соединения

При создании объекта браузер автоматически подключается к серверу, а при обрыве –  соотвественно пытается его возобновить.

Это очень удобно, никакой другой транспорт не обладает такой встроенной способностью.

Как же серверу полностью закрыть соединение?

При любом закрытии соединения, в том случае если сервер ответит на запрос и закроет соединение сам – браузер через короткое время повторит свой запрос.

Есть лишь два способа, которыми сервер может «отшить» надоедливый EventSource:

  • Ответить со статусом отличным от  200.
  • Ответить с Content-Type, не совпадающим с text/event-stream.

Между попытками возобновить соединение будет  небольшая пауза, значение которой зависит от браузера (1-3 секунды) и может быть изменено сервером через указание retry: в ответе:

retry: 15000 data: Поставлена задержка 15 секунд

Браузер, со своей стороны, может закрыть соединение, вызвав  close():

var eventSource = new EventSource(...); eventSource.close();

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

Идентификатор id

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

data: Сообщение 1
id: 1

data: Сообщение 2
id: 2

data: Сообщение 3
data: из двух строк
id: 3

При получении id: браузер:

  • должен установить свойство eventSource.lastEventId в его значение.
  • при пересоединении пошлёт заголовок Last-Event-ID с этим id, так что сервер сможет переслать последующие, пропущенные, сообщения.

Обращаю внимание: id шлётся не перед сообщением, а после него, чтобы обновление lastEventId произошло, когда браузер всё уже точно получил.

Статус соединения readyState

У объекта EventSource есть свойство readyState, которое содержит одно из значений (выдержка из стандарта):

const unsigned short CONNECTING = 0; // в процессе (пере-)соединения 
const unsigned short OPEN = 1; // соединение установлено 
const unsigned short CLOSED = 2; // соединение закрыто

При создании объекта и при разрыве оно автоматически будет CONNECTING.

Читайте также  Атака типа CSRF

События

Событий всего 3:

  • onmessage – пришло сообщение, доступно как event.data
  • onopen – при успешном установлении соединения
  • onerror – при ошибке соединения.

Вот пример:

var eventSource = new EventSource('digits'); 
eventSource.onopen = function(e) { 
console.log("Соединение открыто"); }; 
eventSource.onerror = function(e) { 
if (this.readyState == EventSource.CONNECTING) { 
console.log("Соединение порвалось, пересоединяемся..."); } 
else { console.log("Ошибка, состояние: " + this.readyState); } }; 
eventSource.onmessage = function(e) { console.log("Пришли данные: " + e.data); };

Своё имя события: event

По умолчанию на события срабатывает обработчик onmessage, но можно сделать и свои собственные события. Для этого сервер должен указать перед событием его имя после event:.

Например:

event: join
data: Вася
data: Привет
event: leave
data: Вася

Сообщение по умолчанию имеет имя message.

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

Пример кода для обработки:

eventSource.addEventListener('join', function(e) {
 alert( 'Пришёл ' + e.data ); }); 
eventSource.addEventListener('message', function(e) { 
alert( 'Сообщение ' + e.data ); }); 
eventSource.addEventListener('leave', function(e) { 
alert( 'Ушёл ' + e.data ); });

Кросс-доменность

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

var source = new EventSource("http://site.ru/stream", { withCredentials: true });

Второй аргумент сделан объектом с расчётом на будущее. Пока что никаких других свойств там не поддерживается, только withCredentials.
Сервер при этом получит заголовок Origin с доменом запроса и должен ответить с заголовком Access-Control-Allow-Origin (и Access-Control-Allow-Credentials, если стоит withCredentials), в точности как в уроке XMLHttpRequest: кросс-доменные запросы.

При кросс-доменных запросах у событий event также появится дополнительное свойство origin, содержащее адрес источника, откуда пришли данные. Его можно использовать для дополнительной проверки со стороны браузера:

eventSource.addEventListener('message', function(e) { 
if (e.origin != 'http://webdiz.com.ua') return; alert( 'Сообщение ' + e.data ); });

Итоги

Объект EventSource предназначен для передачи текстовых сообщений с сервера, используя протокол HTTP.

Он предлагает не только передачу сообщений, но и встроенную поддержку важных вспомогательных функций:

  • События event.
  • Автоматическое пересоединение, с настраиваемой задержкой retry.
  • Проверка текущего состояния подключения по  событию readyState.
  • Идентификаторы сообщений id для точного возобновления потока данных, последний полученный идентификатор передаётся в заголовке Last-Event-ID.
  • Кросс-доменность CORS.
Читайте также  Метод fetch как замена XMLHttpRequest

Этот набор функций делает EventSource достойной альтернативой WebSocket, которые хоть и потенциально мощнее, но требуют реализации всех этих функций на клиенте и сервере, поверх протокола.

Поддержка – все браузеры, кроме IE.

  • Синтаксис:
    var source = new EventSource(src[, credentials]); // src - адрес с любого домена
    
  • Второй необязательный аргумент, если указан в виде { withCredentials: true }, инициирует отправку Cookie и данных авторизации при кросс-доменных запросах.Безопасность при кросс-доменных запросах обеспечивается аналогично XMLHttpRequest.
  • Свойства объекта:
    readyState
    Текущее состояние соединения, одно из EventSource.CONNECTING (=0), EventSource.OPEN (=1) или EventSource.CLOSED (=2).
    lastEventId
    Последнее полученное id, если есть. При возобновлении соединения браузер указывает это значение в заголовке Last-Event-ID.
    url, withCredentials
    Параметры, переданные при создании объекта. Менять их нельзя.
  • Методы:
    close()
    Закрывает соединение.
  • События:
    onmessage
    При сообщении, данные – в event.data.
    onopen
    При установлении соединения.
    onerror
    При ошибке, в том числе – закрытии соединения по инициативе сервера.

    Эти события можно ставить напрямую через свойство: source.onmessage = ….

    Если сервер присылает имя события в event:, то такие события нужно обрабатывать через addEventListener.

  • Формат ответа сервера:Сервер присылает пустые строки, либо строки, начинающиеся с:
    • data: – сообщение, несколько таких строк подряд склеиваются и образуют одно сообщение.
    • id: – обновляет lastEventId.
    • retry: – указывает паузу между пересоединениями, в миллисекундах. JavaScript не может указать это значение, только сервер.
    • event: – имя события, должен быть перед data:.

 

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Поделиться

Об авторе

admin administrator

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: