Использование SSE вместо WebSockets для однонаправленного потока данных по HTTP/2
Опубликовано: 2022-03-10При создании веб-приложения необходимо учитывать, какой механизм доставки он собирается использовать. Допустим, у нас есть кроссплатформенное приложение, работающее с данными в реальном времени; приложение фондового рынка, предоставляющее возможность покупать или продавать акции в режиме реального времени. Это приложение состоит из виджетов, которые приносят разную пользу разным пользователям.
Когда дело доходит до доставки данных с сервера клиенту, мы ограничены двумя общими подходами: вытягивание клиента или проталкивание сервером . В качестве простого примера для любого веб-приложения клиент — это веб-браузер. Когда веб-сайт в вашем браузере запрашивает данные у сервера, это называется вытягиванием клиента . Обратное, когда сервер проактивно отправляет обновления на ваш сайт, это называется сервером .
В настоящее время существует несколько способов их реализации:
- Длинный/короткий опрос (вытягивание клиента)
- WebSockets (проталкивание сервера)
- События Server-Sent (отправленные сервером).
Мы собираемся подробно рассмотреть три альтернативы после того, как установим требования для нашего бизнес-кейса.
Бизнес-кейс
Чтобы иметь возможность быстро доставлять новые виджеты для нашего приложения фондового рынка и подключать их без повторного развертывания всей платформы, нам нужно, чтобы они были автономными и управляли собственным вводом-выводом данных. Виджеты никак не связаны друг с другом. В идеальном случае все они собираются подписаться на какую-то конечную точку API и начать получать от нее данные. Помимо более быстрого выхода на рынок новых функций, этот подход дает нам возможность экспортировать контент на сторонние веб-сайты, тогда как наши виджеты приносят все необходимое сами по себе.
Основная ловушка здесь заключается в том, что количество подключений будет расти линейно с количеством виджетов, которые у нас есть, и мы собираемся достичь предела браузеров для количества одновременно обрабатываемых HTTP-запросов.
Данные, которые будут получать наши виджеты, в основном состоят из чисел и их обновлений: Первоначальный ответ содержит десять акций с некоторой рыночной стоимостью для них. Это включает в себя обновления добавления / удаления акций и обновления рыночной стоимости представленных в настоящее время. Мы максимально быстро передаем небольшое количество строк JSON для каждого обновления.
HTTP/2 обеспечивает мультиплексирование запросов, поступающих из одного и того же домена, то есть мы можем получить только одно соединение для нескольких ответов. Похоже, это может решить нашу проблему. Мы начнем с изучения различных вариантов получения данных и посмотрим, что мы можем из них извлечь.
- Мы собираемся использовать NGINX для балансировки нагрузки и прокси, чтобы скрыть все наши конечные точки за одним и тем же доменом. Это позволит нам использовать мультиплексирование HTTP/2 из коробки.
- Мы хотим эффективно использовать сеть и батарею мобильных устройств.
Альтернативы
Длинный опрос

Привлечение клиента — это программная реализация, эквивалентная надоедливому ребенку, сидящему на заднем сиденье вашего автомобиля и постоянно спрашивающему: «Мы уже на месте?» Короче говоря, клиент запрашивает данные у сервера. Сервер не имеет данных и ждет некоторое время перед отправкой ответа:
- Если во время ожидания что-то всплывает, сервер отправляет это и закрывает запрос;
- Если отправлять нечего и достигнуто максимальное время ожидания, сервер отправляет ответ, что данных нет;
- В обоих случаях клиент открывает следующий запрос данных;
- Вспеньте, промойте, повторите.
Вызовы AJAX работают по протоколу HTTP, что означает, что запросы к одному и тому же домену должны мультиплексироваться по умолчанию. Однако мы столкнулись с множеством проблем, пытаясь заставить это работать должным образом. Некоторые из подводных камней, которые мы выявили в нашем подходе к виджетам:
Заголовки
Каждый запрос опроса и ответ представляют собой полное HTTP-сообщение и содержат полный набор HTTP-заголовков во фрейме сообщения. В нашем случае, когда у нас есть небольшие частые сообщения, заголовки фактически представляют больший процент передаваемых данных. Фактическая полезная нагрузка намного меньше, чем общее количество переданных байтов (например, 15 КБ заголовков на 5 КБ данных).Максимальная задержка
После ответа сервера он больше не может отправлять данные клиенту до тех пор, пока клиент не отправит следующий запрос. В то время как средняя задержка для длительного опроса близка к одному сетевому транзиту, максимальная задержка превышает три сетевых транзита: ответ, запрос, ответ. Однако из-за потери пакетов и повторной передачи максимальная задержка для любого протокола TCP/IP будет превышать три сетевых транзита (этого можно избежать при конвейерной обработке HTTP). В то время как при прямом подключении к локальной сети это не является большой проблемой, она становится проблемой, когда вы находитесь в движении и переключаете сетевые ячейки. В определенной степени это наблюдается с SSE и WebSockets, но наибольший эффект наблюдается при опросе.Установление соединения
Хотя этого можно избежать, используя постоянное HTTP-соединение, повторно используемое для многих запросов опроса, сложно соответствующим образом синхронизировать все ваши компоненты для опроса в короткие промежутки времени, чтобы поддерживать соединение активным. В конце концов, в зависимости от ответов сервера, ваши опросы будут рассинхронизированы.Снижение производительности
Долго опрашивающий клиент (или сервер), который находится под нагрузкой, имеет естественную тенденцию к снижению производительности за счет задержки сообщений. Когда это произойдет, события, отправленные клиенту, будут поставлены в очередь. Это действительно зависит от реализации; в нашем случае нам нужно агрегировать все данные, поскольку мы отправляем события добавления/удаления/обновления нашим виджетам.Тайм-ауты
Длинные запросы опроса должны оставаться в ожидании, пока сервер не отправит что-то клиенту. Это может привести к тому, что соединение будет закрыто прокси-сервером, если оно будет бездействовать слишком долго.Мультиплексирование
Это может произойти, если ответы происходят одновременно через постоянное соединение HTTP/2. Это может быть сложно сделать, поскольку ответы на опросы не могут быть синхронизированы.
Подробнее о реальных проблемах, которые могут возникнуть при длительном опросе, можно узнать здесь .
Веб-сокеты

В качестве первого примера метода проталкивания сервером мы рассмотрим WebSockets.
Через МДН:
WebSockets — это передовая технология, позволяющая открывать интерактивный сеанс связи между браузером пользователя и сервером. С помощью этого API вы можете отправлять сообщения на сервер и получать ответы, управляемые событиями, без необходимости запрашивать ответ у сервера.
Это протокол связи, обеспечивающий полнодуплексные каналы связи по одному TCP-соединению.
И HTTP, и WebSockets расположены на уровне приложений из модели OSI и, таким образом, зависят от TCP на уровне 4.
- Заявление
- Презентация
- Сессия
- Транспорт
- Сеть
- Канал передачи данных
- Физический
В RFC 6455 говорится, что WebSocket «предназначен для работы через HTTP-порты 80 и 443, а также для поддержки HTTP-прокси и посредников», что делает его совместимым с протоколом HTTP. Для достижения совместимости рукопожатие WebSocket использует заголовок HTTP Upgrade для перехода с протокола HTTP на протокол WebSocket.
В Википедии также есть очень хорошая статья, в которой объясняется все, что вам нужно знать о WebSockets. Я призываю вас прочитать его.
Убедившись, что сокеты действительно могут работать на нас, мы начали изучать их возможности в нашем бизнес-кейсе и преодолевали стену за стеной за стеной.
Прокси-серверы . Как правило, с WebSockets и прокси-серверами возникает несколько разных проблем:
- Первый связан с интернет-провайдерами и тем, как они управляют своими сетями. Проблемы с прокси-серверами радиуса, заблокированными портами и так далее.
- Второй тип проблем связан с тем, как прокси-сервер настроен для обработки незащищенного HTTP-трафика и долгоживущих соединений (влияние уменьшается при использовании HTTPS).
- Третья проблема: «с WebSockets вы вынуждены использовать TCP-прокси, а не HTTP-прокси. Прокси-серверы TCP не могут вставлять заголовки, переписывать URL-адреса или выполнять многие из ролей, о которых мы традиционно берем на себя наши прокси-серверы HTTP».
Количество подключений : известное ограничение на количество подключений для HTTP-запросов, которое вращается вокруг числа 6, не распространяется на WebSockets. 50 сокетов = 50 соединений. Десять вкладок браузера на 50 сокетов = 500 подключений и так далее. Поскольку WebSocket — это другой протокол для доставки данных, он не мультиплексируется автоматически через соединения HTTP/2 (на самом деле он вообще не работает поверх HTTP). Реализация пользовательского мультиплексирования как на сервере, так и на клиенте слишком сложна, чтобы сделать сокеты полезными в конкретном бизнес-кейсе. Кроме того, это связывает наши виджеты с нашей платформой, поскольку им потребуется какой-то API на клиенте для подписки, и мы не можем распространять их без него.
Балансировка нагрузки (без мультиплексирования) : если каждый пользователь открывает
n
сокетов, правильная балансировка нагрузки будет очень сложной. Когда ваши серверы перегружены, и вам нужно создать новые экземпляры и завершить работу старых в зависимости от реализации вашего программного обеспечения, действия, предпринимаемые при «повторном подключении», могут вызвать массивную цепочку обновлений и новых запросов данных, которые перегрузят вашу систему. . WebSockets необходимо поддерживать как на сервере, так и на клиенте. Невозможно переместить соединения сокетов на другой сервер, если текущий испытывает высокую нагрузку. Они должны быть закрыты и вновь открыты.DoS : это обычно обрабатывается интерфейсными прокси-серверами HTTP, которые не могут обрабатываться прокси-серверами TCP, которые необходимы для WebSockets. Можно подключиться к сокету и начать заливать ваши серверы данными. Веб-сокеты делают вас уязвимыми для таких атак.
Изобретение велосипеда : с WebSockets приходится решать множество проблем, которые решаются в HTTP самостоятельно.
Подробнее о реальных проблемах с WebSockets можно прочитать здесь.
Некоторыми хорошими вариантами использования WebSockets являются чаты и многопользовательские игры, в которых преимущества перевешивают проблемы с реализацией. Поскольку их основным преимуществом является дуплексная связь, а нам она на самом деле не нужна, нам нужно двигаться дальше.
Влияние
Мы получаем увеличение операционных издержек с точки зрения разработки, тестирования и масштабирования; программное обеспечение и его ИТ-инфраструктура с использованием опросов и веб-сокетов.
Мы получаем ту же проблему с мобильными устройствами и сетями с обоими. Аппаратная конструкция этих устройств поддерживает открытое соединение, сохраняя работоспособность антенны и соединения с сотовой сетью. Это приводит к сокращению времени автономной работы, нагреву и, в некоторых случаях, к дополнительной плате за передачу данных.
Но почему у нас все еще есть проблемы с мобильными устройствами?
Рассмотрим, как мобильное устройство по умолчанию подключается к Интернету:
Простое объяснение того, как работает мобильная сеть: обычно мобильные устройства имеют маломощную антенну, которая может принимать данные из соты. Таким образом, как только устройство получает данные о входящем звонке, оно запускает полнодуплексную антенну, чтобы установить соединение. Одна и та же антенна используется всякий раз, когда вы хотите позвонить или выйти в Интернет (если Wi-Fi недоступен). Полнодуплексной антенне необходимо установить соединение с сотовой сетью и выполнить некоторую аутентификацию. Как только соединение установлено, между вашим устройством и ячейкой устанавливается некоторая связь для выполнения нашего сетевого запроса. Нас перенаправляют на внутренний прокси провайдера мобильной связи, который обрабатывает интернет-запросы. С этого момента процедура уже известна: он спрашивает DNS, где на самом деле находится www.domainname.ext
, получает URI ресурса и в конечном итоге перенаправляется на него.
Этот процесс, как вы могли себе представить, потребляет довольно много энергии аккумулятора. По этой причине производители мобильных телефонов дают несколько дней в режиме ожидания, а в режиме разговора всего пару часов.
Без WiFi и WebSockets, и опрос требуют, чтобы полнодуплексная антенна работала почти постоянно. Таким образом, мы сталкиваемся с повышенным потреблением данных и повышенным энергопотреблением, а также, в зависимости от устройства, и выделением тепла.

К тому времени, когда все станет мрачным, похоже, нам придется пересмотреть бизнес-требования для нашего приложения. Мы что-то упускаем?
ССЭ

Через МДН:
«Интерфейс EventSource используется для получения событий, отправленных сервером. Он подключается к серверу через HTTP и получает события в формате текста/потока событий, не закрывая соединение».
Основное отличие от опроса в том, что мы получаем только одно соединение и сохраняем поток событий, проходящий через него. Длительный опрос создает новое соединение для каждого извлечения — следовательно, накладные расходы на заголовки и другие проблемы, с которыми мы столкнулись.
Через html5doctor.com:
События, отправленные сервером, — это события в реальном времени, отправляемые сервером и получаемые браузером. Они похожи на WebSockets тем, что происходят в режиме реального времени, но в значительной степени представляют собой односторонний метод связи с сервером.
Это выглядит немного странно, но после рассмотрения — наш основной поток данных идет от сервера к клиенту и в гораздо меньшем количестве случаев от клиента к серверу.
Похоже, мы можем использовать это для нашего основного бизнес-кейса по доставке данных. Мы можем разрешить покупки клиентов, отправив новый запрос, поскольку протокол является однонаправленным, и клиент не может отправлять сообщения на сервер через него. В конечном итоге это приведет к задержке загрузки полнодуплексной антенны на мобильных устройствах. Однако мы можем мириться с тем, что это происходит время от времени — в конце концов, эта задержка измеряется миллисекундами.
Уникальные черты
- Поток подключения исходит от сервера и доступен только для чтения.
- Они используют обычные HTTP-запросы для постоянного соединения, а не специальный протокол. Получение мультиплексирования через HTTP/2 из коробки.
- Если соединение разрывается, EventSource запускает событие ошибки и автоматически пытается восстановить соединение. Сервер также может контролировать тайм-аут перед тем, как клиент попытается повторно подключиться (более подробно это будет объяснено позже).
- Клиенты могут отправлять уникальный идентификатор с сообщениями. Когда клиент пытается повторно подключиться после разрыва соединения, он отправляет последний известный идентификатор. Затем сервер может увидеть, что клиент пропустил
n
сообщений, и отправить список пропущенных сообщений при повторном подключении.
Пример реализации клиента
Эти события аналогичны обычным событиям JavaScript, происходящим в браузере, например событиям кликов, за исключением того, что мы можем управлять именем события и связанными с ним данными.
Давайте посмотрим на простой предварительный просмотр кода для клиентской стороны:
// subscribe for messages var source = new EventSource('URL'); // handle messages source.onmessage = function(event) { // Do something with the data: event.data; };
Что мы видим из примера, так это то, что клиентская сторона довольно проста. Он подключается к нашему источнику и ожидает получения сообщений.
Чтобы серверы могли отправлять данные на веб-страницы через HTTP или с использованием выделенных серверных протоколов, спецификация вводит интерфейс `EventSource` на клиенте. Использование этого API состоит из создания объекта EventSource и регистрации прослушивателя событий.
Реализация клиента для WebSockets очень похожа на эту. Сложность с сокетами заключается в ИТ-инфраструктуре и реализации сервера.
источник событий
Каждый объект EventSource
имеет следующие члены:
- URL: указан во время строительства.
- Запрос: изначально имеет значение null.
- Время переподключения: значение в мс (значение, определяемое агентом пользователя).
- Идентификатор последнего события: изначально пустая строка.
- Состояние готовности: состояние соединения.
- ПОДКЛЮЧЕНИЕ (0)
- ОТКРЫТЫЙ (1)
- ЗАКРЫТО (2)
За исключением URL-адреса, все они рассматриваются как частные и недоступны извне.
Встроенные события:
- Открыть
- Сообщение
- Ошибка
Обработка разрывов соединения
Соединение автоматически восстанавливается браузером, если оно прерывается. Сервер может отправить тайм-аут для повторной попытки или навсегда закрыть соединение. В таком случае браузер либо попытается переподключиться после тайм-аута, либо не попытается вообще, если соединение получило сообщение о завершении. Кажется довольно простым — и это действительно так.
Пример реализации сервера
Ну, если клиент такой простой, может быть, реализация сервера сложна?
Ну, серверный обработчик для SSE может выглядеть так:
function handler(response) { // setup headers for the response in order to get the persistent HTTP connection response.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // compose the message response.write('id: UniqueID\n'); response.write("data: " + data + '\n\n'); // whenever you send two new line characters the message is sent automatically }
Мы определяем функцию, которая будет обрабатывать ответ:
- Заголовки настройки
- Создать сообщение
- Отправлять
Обратите внимание, что вы не видите вызов метода send()
или push()
. Это связано с тем, что стандарт определяет, что сообщение будет отправлено, как только оно получит два символа \n\n
, как в примере: response.write("data: " + data + '\n\n');
. Это немедленно отправит сообщение клиенту. Обратите внимание, что data
должны быть экранированной строкой и не иметь в конце символов новой строки.
Конструкция сообщения
Как упоминалось ранее, сообщение может содержать несколько свойств:
- Я БЫ
Если значение поля не содержит U+0000 NULL, установите последний буфер идентификатора события в значение поля. В противном случае игнорируйте поле. - Данные
Добавьте значение поля в буфер данных, затем добавьте один символ U+000A LINE FEED (LF) в буфер данных. - Мероприятие
Установите буфер типа события на значение поля. Это приводит к тому, чтоevent.type
получает имя вашего пользовательского события. - Повторить попытку
Если значение поля состоит только из цифр ASCII, интерпретируйте значение поля как целое число с основанием десять и установите время повторного подключения потока событий к этому целому числу. В противном случае игнорируйте поле.
Все остальное будет проигнорировано. Мы не можем вводить свои собственные поля.
Пример с добавленным event
:
response.write('id: UniqueID\n'); response.write('event: add\n'); response.write('retry: 10000\n'); response.write("data: " + data + '\n\n');
Затем на клиенте это обрабатывается с помощью addEventListener
как такового:
source.addEventListener("add", function(event) { // do stuff with data event.data; });
Вы можете отправлять несколько сообщений, разделенных новой строкой, если вы предоставляете разные идентификаторы.
... id: 54 event: add data: "[{SOME JSON DATA}]" id: 55 event: remove data: JSON.stringify(some_data) id: 56 event: remove data: { data: "msg" : "JSON data"\n data: "field": "value"\n data: "field2": "value2"\n data: }\n\n ...
Это значительно упрощает то, что мы можем делать с нашими данными.
Особые требования к серверу
Во время нашего POC для серверной части мы определили, что у нее есть некоторые особенности, которые необходимо учитывать, чтобы иметь рабочую реализацию SSE. В лучшем случае вы будете использовать сервер на основе цикла событий, такой как NodeJS, Kestrel или Twisted. Идея состоит в том, что с решением на основе потоков у вас будет поток на соединение → 1000 соединений = 1000 потоков. С решением цикла обработки событий у вас будет один поток на 1000 подключений.
- Вы можете принимать запросы EventSource только в том случае, если в HTTP-запросе указано, что он может принимать тип MIME потока событий;
- Вам необходимо вести список всех подключенных пользователей, чтобы создавать новые события;
- Вы должны прослушивать обрыв соединения и удалять их из списка подключенных пользователей;
- При желании вы должны вести историю сообщений, чтобы повторно подключающиеся клиенты могли наверстать пропущенные сообщения.
Он работает так, как ожидалось, и сначала выглядит как волшебство. Мы получаем все, что хотим, чтобы наше приложение работало эффективно. Как и все вещи, которые выглядят слишком хорошо, чтобы быть правдой, мы иногда сталкиваемся с некоторыми проблемами, которые необходимо решить. Однако их не сложно реализовать или обойти:
Известно, что устаревшие прокси-серверы в некоторых случаях прерывают HTTP-соединения после короткого тайм-аута. Для защиты от таких прокси-серверов авторы могут включать строку комментария (начинающуюся с символа ':') каждые 15 секунд или около того.
Авторы, желающие связать соединения источников событий друг с другом или с конкретными ранее обслуживаемыми документами, могут обнаружить, что использование IP-адресов не работает, поскольку отдельные клиенты могут иметь несколько IP-адресов (из-за наличия нескольких прокси-серверов), а отдельные IP-адреса могут иметь несколько клиентов (из-за совместного использования прокси-сервера). Лучше включать уникальный идентификатор в документ при его обслуживании, а затем передавать этот идентификатор как часть URL-адреса при установлении соединения.
Авторов также предупреждают, что разбиение на фрагменты HTTP может иметь неожиданные отрицательные последствия для надежности этого протокола, в частности, если разбиение на фрагменты выполняется другим уровнем, не знающим о требованиях по времени. Если это проблема, можно отключить фрагментацию для обслуживания потоков событий.
Клиенты, которые поддерживают ограничение HTTP-подключения к серверу, могут столкнуться с проблемами при открытии нескольких страниц с сайта, если каждая страница имеет EventSource для одного и того же домена. Авторы могут избежать этого, используя относительно сложный механизм использования уникальных доменных имен для каждого соединения, или позволяя пользователю включать или отключать функции EventSource для каждой страницы, или путем совместного использования одного объекта EventSource с помощью общего рабочего процесса.
Поддержка браузера и полифиллы: Edge отстает от этой реализации, но доступен полифилл, который может вас спасти. Тем не менее, наиболее важным аргументом в пользу SSE являются мобильные устройства, где у IE/Edge нет реальной доли рынка.
Некоторые из доступных полифиллов:
- Зеленый дятел
- амвтек
- Реми
Push без подключения и другие функции
Пользовательские агенты, работающие в контролируемых средах, например, браузеры на мобильных телефонах, привязанных к определенным операторам связи, могут переложить управление соединением на прокси-сервер в сети. В такой ситуации пользовательский агент для целей соответствия считается включающим как программное обеспечение телефона, так и сетевой прокси.
Например, браузер на мобильном устройстве после установления соединения может определить, что он находится в поддерживающей сети, и запросить, чтобы прокси-сервер в сети взял на себя управление соединением. Временная шкала для такой ситуации может быть следующей:
- Браузер подключается к удаленному HTTP-серверу и запрашивает ресурс, указанный автором в конструкторе EventSource.
- Сервер время от времени отправляет сообщения.
- Между двумя сообщениями браузер обнаруживает, что он бездействует, за исключением сетевой активности, связанной с поддержанием TCP-соединения в рабочем состоянии, и решает переключиться в спящий режим для экономии энергии.
- Браузер отключается от сервера.
- Браузер связывается со службой в сети и запрашивает, чтобы служба, «push-прокси», вместо этого поддерживала соединение.
- Служба «push proxy» связывается с удаленным HTTP-сервером и запрашивает ресурс, указанный автором в конструкторе EventSource (возможно, включая HTTP-заголовок
Last-Event-ID
и т. д.). - Браузер позволяет мобильному устройству перейти в спящий режим.
- Сервер отправляет другое сообщение.
- Служба «push proxy» использует такую технологию, как OMA push, для передачи события на мобильное устройство, которое выходит из спящего режима только для обработки события, а затем возвращается в спящий режим.
Это может уменьшить общее использование данных и, следовательно, может привести к значительной экономии энергии.
Помимо реализации существующего API и формата передачи текста/потока событий, как определено спецификацией, и более распределенными способами (как описано выше), могут поддерживаться форматы кадрирования событий, определенные другими применимыми спецификациями.
Резюме
После долгих и исчерпывающих POC, включая реализацию сервера и клиента, похоже, что SSE — это ответ на наши проблемы с доставкой данных. С этим тоже есть некоторые подводные камни, но их оказалось тривиально исправить.
Вот так в итоге выглядит наша производственная установка:

Получаем от NGINX следующее:
- Прокси к конечным точкам API в разных местах;
- HTTP/2 и все его преимущества, такие как мультиплексирование соединений;
- Балансировка нагрузки;
- SSL.
Таким образом, мы управляем доставкой данных и сертификатами в одном месте, а не на каждой конечной точке отдельно.
Основные преимущества, которые мы получаем от такого подхода:
- Данные эффективные;
- более простая реализация;
- Он автоматически мультиплексируется через HTTP/2;
- Ограничивает количество подключений для данных на клиенте до одного;
- Предоставляет механизм для экономии заряда батареи за счет разгрузки соединения с прокси-сервером.
SSE — это не просто жизнеспособная альтернатива другим методам доставки быстрых обновлений; похоже, что он находится в своей собственной лиге, когда дело доходит до оптимизации для мобильных устройств. В его реализации нет накладных расходов по сравнению с альтернативами. С точки зрения реализации на стороне сервера это не сильно отличается от опроса. На клиенте это намного проще, чем опрос, поскольку для этого требуется первоначальная подписка и назначение обработчиков событий — очень похоже на то, как управляются WebSockets.
Посмотрите демонстрацию кода, если вы хотите получить простую реализацию клиент-сервер.
Ресурсы
- «Известные проблемы и рекомендации по использованию длинных опросов и потоковой передачи в двунаправленном HTTP», IETF (PDF)
- Рекомендация W3C, W3C
- «Выживет ли WebSocket HTTP/2?», — Аллан Денис, InfoQ.
- «Потоковая передача обновлений с помощью событий, отправленных сервером», Эрик Бидельман, HTML5 Rocks
- «Приложения для передачи данных с HTML5 SSE», Даррен Кук, O'Reilly Media.