- События на стороне сервера
Server Side Events -- события с сервера
Сразу заметим, что на текущий момент этот способ поддерживают все современные браузеры, кроме IE.
Современный стандарт Server-Sent Events позволяет браузеру создавать специальный объект EventSource
, который сам обеспечивает соединение с сервером, делает пересоединение в случае обрыва и генерирует события при поступлении данных.
Он, по дизайну, может меньше, чем WebSocket’ы.
С другой стороны, Server Side Events проще в реализации, работают по обычному протоколу HTTP и сразу поддерживают ряд возможностей, которые для WebSocket ещё надо реализовать.
Поэтому в тех случаях, когда нужна преимущественно односторонняя передача данных от сервера к браузеру, они могут быть удачным выбором.
Получение сообщений
При создании объекта 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:
. Если после двоеточия есть пробел, то он игнорируется.
· Сообщения разделяются двумя строками \n\n
.
· Если нужно переслать перевод строки, то сообщение разделяется. Каждая следующая строка пересылается отдельным data:
.
В частности, две последние строки в примере выше составляют одно сообщение: "Сообщение 3\nиз двух строк"
.
Здесь все очень просто и удобно, кроме разделения сообщения при переводе строки. Но, если подумать – это не так уж страшно: на практике сложные сообщения обычно передаются в формате JSON. А перевод строки в нём кодируется как \n
.
Соответственно, многострочные данные будут пересылаться так:
data: {"user":"Вася","message":"Сообщение 3\n из двух строк"}
…То есть, строка data:
будет одна, и никаких проблем с разделением сообщения нет.
Восстановление соединения
При создании объекта браузер автоматически подключается к серверу, а при обрыве – пытается его возобновить.
Это очень удобно, никакой другой транспорт не обладает такой встроенной способностью.
Как серверу полностью закрыть соединение?
При любом закрытии соединения, в том числе если сервер ответит на запрос и закроет соединение сам – браузер через короткое время повторит свой запрос.
Есть лишь два способа, которыми сервер может «отшить» надоедливый 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
.
События
Событий всего три:
· 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
предназначен для передачи текстовых сообщений с сервера, используя обычный протокол HTTP.
Он предлагает не только передачу сообщений, но и встроенную поддержку важных вспомогательных функций:
· События event
.
· Автоматическое пересоединение, с настраиваемой задержкой retry
.
· Проверка текущего состояния подключения по readyState
.
· Идентификаторы сообщений id
для точного возобновления потока данных, последний полученный идентификатор передаётся в заголовке Last-Event-ID
.
· Кросс-доменность CORS.
Этот набор функций делает EventSource достойной альтернативой WebSocket, которые хоть и потенциально мощнее, но требуют реализации всех этих функций на клиенте и сервере, поверх протокола.
Поддержка – все браузеры, кроме IE.
· Синтаксис:
varsource
= 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
.
· Формат ответа сервера:
Сервер присылает пустые строки, либо строки, начинающиеся с:
o data:
– сообщение, несколько таких строк подряд склеиваются и образуют одно сообщение.
o id:
– обновляет lastEventId
.
o retry:
– указывает паузу между пересоединениями, в миллисекундах. JavaScript не может указать это значение, только сервер.
o event:
– имя события, должен быть перед data:
.