- События на стороне сервера
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:.
