Мы быстро движемся к ориентированному на события миру потоков данных и API. События, отправляемые сервером, занимают определенную нишу в этом новом мире: открытый, легкий, протокол только для подписки для управляемых событиями потоков данных. В этой статье рассматривается, как появился SSE, как он работает под капотом и почему он быстро внедряется разработчиками.
Немного фона
SSE основан на так называемых событиях DOM-событий, отправленных сервером, которые впервые были реализованы в Opera 9. Идея проста: браузер может подписаться на поток событий, генерируемых сервером, получая обновления всякий раз, когда происходит новое событие. Это привело к появлению популярного EventSource
интерфейса, который принимает соединение с потоком HTTP и сохраняет соединение открытым, извлекая из него доступные данные. Соединение остается открытым до тех пор, пока оно не будет закрыто по телефону EventSource.close()
.
Что такое отправленные сервером события (SSE)?
SSE — это стандарт, описывающий, как серверы могут инициировать передачу данных клиентам после установления первоначального клиентского соединения. Это обеспечивает эффективную для памяти реализацию потоковой передачи XHR. В отличие от необработанного XHR-соединения, которое буферизирует полный полученный ответ до тех пор, пока соединение не будет разорвано, соединение SSE может отбрасывать обработанные сообщения, не накапливая их все в памяти.
SSE предназначен для использования JavaScript EventSource API для подписки на поток данных в любом популярном браузере. Через этот интерфейс клиент запрашивает определенный URL-адрес для получения потока событий. SSE обычно используется для отправки обновлений сообщений или непрерывных потоков данных клиенту браузера.
Короче говоря, отправленное сервером событие — это когда обновления отправляются (а не извлекаются или запрашиваются) с сервера в браузер.
Как работает SSE?
Соединение через SSE обычно начинается с инициируемой клиентом связи между клиентом и сервером. Клиент создает новый объект JavaScript EventSource, передавая URL-адрес конечной точки серверу по обычному HTTP-запросу. Клиент ожидает ответ с потоком сообщений о событиях с течением времени.
Сервер оставляет HTTP-ответ открытым, пока у него больше нет событий для отправки, он решает, что соединение было открыто достаточно долго, и его можно считать устаревшим или пока клиент явно не закроет начальный запрос.
Как работает SSE от запроса на соединение до закрытия.
Вот быстрый пример открытия потока через SSE:
var source = new EventSource('URL_TO_EVENT_STREAM');
source.onopen = function() {
console.log('connection to stream has been opened');
};
source.onerror = function (error) {
console.log('An error has occurred while receiving stream', error);
};
source.onmessage = function (stream) {
console.log('received stream', stream);
};
Почему вы используете SSE?
В идеале, при запросе данных с сервера, просто XMLHttpRequest
подойдет. Однако существуют сценарии, в которых вы хотите сохранить соединение открытым с использованием потоковой передачи XHR. Но это приносит свой собственный набор издержек, включая обработку логики синтаксического анализа и управление соединением.
Вот тут-то и приходит SSE. SSE обеспечивает уровень логики управления соединением и синтаксического анализа, что позволяет нам легко сохранять соединение открытым, пока сервер передает новые события клиенту по мере их появления.
Когда бы вы использовали SSE?
Природа обмена сообщениями в реальном времени и потоковых данных означает, что разные протоколы служат различным целям лучше, чем другие. Для мультиплексной, двунаправленной потоковой передачи WebSockets идеально подходит. Для устройств IoT с ограниченным временем автономной работы MQTT больше подходит. Но иногда это излишне.
SSE идеально подходит для таких сценариев, как:
- Когда необходим эффективный однонаправленный протокол связи, который не добавит ненужной нагрузки на сервер (что и происходит при длительном опросе )
- Когда вам нужен протокол с предопределенным стандартом для обработки ошибок
- Когда вы хотите использовать HTTP-методы для потоковой передачи данных в реальном времени
- Когда вам нужен однонаправленный протокол с большей задержкой для пользователей, чем другие способы потоковой передачи данных на основе HTTP
Вот несколько примеров, когда SSE уже используется:
- Подписка на канал криптовалюты или цены на акции
- Подписка на канал Twitter
- Получение живых спортивных результатов
- Новости обновления или оповещения
Что нужно учитывать в SSE
Как и во всем, у SSE есть свои проблемы.
Основным ограничением SSE является то, что он является однонаправленным, поэтому нет возможности передавать информацию на сервер от клиента. Единственный способ передать дополнительную информацию во время соединения, что многие разработчики предпочитают делать со строками запроса. Например, если указан URL-адрес потока http://example.com/sse
, разработчики могут добавить строку запроса для передачи такой информации, как идентификатор пользователя http://example.com/sse?userid=891
.
Эта однонаправленность вызывает дополнительную проблему: когда клиент теряет соединение, нет надежного способа сообщить серверу, как нет способа выполнить тактовые импульсы от клиента к серверу. Поскольку SSE основан на TCP / IP, существует механизм, который предупреждает сервер, когда клиент теряет соединение. Но это не всегда работает хорошо, поэтому сервер не всегда сразу понимает, что соединение потеряно. Однако это незначительное ограничение для SSE.
Поддержка SSE
SSE широко поддерживается в популярных браузерах, что означает, что он поддерживается рядом мобильных и встроенных устройств IoT. Тем не менее, прежде чем пытаться внедрить SSE, стоит проверить покрытие поддержки для браузеров, вашего приложения или службы поддержки.
Браузеры, которые поддерживают SSE по состоянию на июнь 2019 года.
Стоит также проверить EventSource
поддержку. Смотрите пример ниже, используя polyfill:
if ('EventSource' in window) {
// use polyfills
}
var source = new EventSource('URL');
Отличным примером polyfill для EventSource
является полифилл EventSource от Yaffle .
Использование SSE в масштабе
Поскольку SSE основан на протоколе HTTP, масштабирование может быть достигнуто с помощью таких средств, как балансировка нагрузки. Однако вам необходимо обеспечить какой-то общий ресурс за серверами, чтобы они все синхронизировались с новыми обновлениями событий.
Многие предпочитают обрабатывать это с помощью шаблона проектирования архитектуры публикации / подписки . В pub / sub события не отправляются напрямую клиентам, а отправляются брокеру. Затем брокер отправляет сообщение всем подписчикам (которые могут включать в себя первоначального отправителя), и они отправляют его клиентам. Чтобы разместить свой собственный механизм pub / sub, вы можете рассмотреть возможность использования Redis.
Если вы предпочитаете снять эту сложность масштабирования и сосредоточиться на разработке основного продукта, вы можете положиться на платформы обмена сообщениями в реальном времени, которые предлагают конечные точки потока событий и SSE. Ознакомьтесь с этим подробным объяснением того, как реализовать SSE, используя платформу обмена сообщениями в реальном времени, которая поддерживает протокол.
Как использовать отправленные сервером события наилучшим образом
В то время как SSE прост в реализации, разработчики часто застревают при отправке данных. SSE представляет данные в реальном потоке, поэтому если нам нужно отправить JSON с SSE, это выглядит примерно так:
res.write('data: {\n');
res.write('data: "foo": "bar",\n');
res.write('data: "baz", 555\n');
res.write('data: }\n\n');
Для разработчиков, которые привыкли писать чистый JavaScript, при реализации SSE они могут получить недопустимый JSON при анализе на стороне клиента. Использование библиотеки, которая абстрагирует это с помощью простого JavaScript, может помочь. Например, использование express-sse
библиотеки при использовании Express в качестве веб-платформы. Вот пример SSE с сервера, использующего Node’s Express:
app.get('/stream', (req, res)=>{
res.status(200).set({
"connection": "keep-alive",
"content-type": "text/event-stream"
});
res.write(`data: Hello there \n\n`);
});
Глядя на код выше, обратите внимание на три вещи:
Text/event-stream content
введите заголовок. Это то, что браузеры ищут для подтверждения потока событий.Keep-alive
заголовок. Это говорит браузеру не закрывать соединение.- Двойные символы новой строки в конце данных. Это указывает на конец сообщения.
Однако, глядя на приведенный выше пример, это полезно только при отправке одного события или использовании setInterval
функции для постоянной проверки базы данных на наличие новых соединений.
express-sse
Например, если мы будем использовать библиотеку, она предоставляет метод, с помощью которого мы можем передавать поток из любого места в нашем приложении, а не обязательно маршрут потока событий.
var SSE = require('express-sse');
var sse = new SSE();
app.get('/stream', sse.init);
// we can always now call sse.send from anywhere the sse variable is available, and see the result in our stream.
let content = 'Test data at ' + JSON.stringify(Date.now());
sse.send(content);
Поддержка отправленных сервером событий
Хотя SSE является легковесным протоколом как с точки зрения реализации, так и с точки зрения использования, при работе с потоками, управляемыми событиями, в масштабе все еще существуют инфраструктурные соображения. И поскольку мы продолжаем двигаться к миру управляемых событиями потоков данных, приложений, сервисов и API-интерфейсов, SSE не всегда будет правильным выбором протокола или удовлетворения ваших потребностей в потоковой передаче. Сейчас, как никогда раньше, построение на платформе, способной обеспечить безопасность вашей инфраструктуры в будущем, — это то, что даже технические гиганты знают, что это лучший выбор.