При создании веб-приложения необходимо учитывать, какой механизм доставки они собираются использовать. Допустим, у нас есть кросс-платформенный приложение, которое работает с данными в режиме реального времени; приложение фондового рынка, предоставляющее возможность покупать или продавать акции в режиме реального времени. Это приложение состоит из виджетов, которые приносят различную ценность для различных пользователей.
Когда дело доходит до доставки данных от сервера к клиенту, мы ограничены двумя общими подходами: вытягивание клиента или нажатие сервера. В качестве простого примера с любым веб-приложением, клиент веб-браузер. Когда веб-сайт в вашем браузере запрашивает у сервера данные, это называется вытягивание клиента. Наоборот, когда сервер активно продвигает обновления на ваш веб-сайт, он называется нажатием сервера.
В настоящее время существует несколько способов реализации этих проблем:
- Длинный/короткий опрос (клиентское притяжение)
- WebSockets (серверный толчок)
- События, отправляемые сервером (нажатие сервера).
Мы собираемся взять углубленный взгляд на три альтернативы после того, как мы установили требования для нашего бизнес-кейса.
Бизнес-дело
Для того, чтобы иметь возможность поставлять новые виджеты для нашего приложения фондового рынка быстро и plug’n’play их без передислокации всей платформы, мы должны их быть автономными и управлять своими собственными данными вв/о. Виджеты не связаны друг с другом в любом случае. В идеальном случае, все они собираются подписаться на некоторые конечные точки API и начать получать данные из него. Помимо более быстрого времени на рынок новых функций, этот подход дает нам возможность экспортировать контент на сторонние веб-сайты, в то время как наши виджеты приносят все, что им нужно самостоятельно.
Основная ловушка здесь заключается в том, что количество подключений будет расти линейно с числом виджетов у нас есть, и мы собираемся ударить предел браузеров для числа запросов HTTP обрабатываются сразу.
Данные, которые наши виджеты будут получать, в основном состоят из чисел и обновлений их числа: первоначальный ответ содержит десять акций с некоторыми рыночными значениями для них. Это включает в себя обновления добавления/ удаления акций и обновления рыночных значений представленных в настоящее время них. Мы передаем небольшое количество строк JSON для каждого обновления как можно быстрее.
HTTP/2 обеспечивает мультиплексирование запросов, поступающих из одного и того же домена, что означает, что мы можем получить только одно соединение для нескольких ответов. Это звучит, как он может решить нашу проблему. Мы начинаем с изучения различных вариантов, чтобы получить данные и посмотреть, что мы можем получить от них.
- Мы будем использовать NGINX для балансировки нагрузки и прокси, чтобы скрыть все наши конечные точки за один и тот же домен. Это позволит нам использовать http/2 мультиплексирование из коробки.
- Мы хотим эффективно использовать сеть и аккумулятор мобильных устройств.
Альтернативы
Долгий опрос
Клиент тянуть является осуществление программного обеспечения эквивалент раздражает ребенка, сидящего на заднем сиденье вашего автомобиля постоянно спрашивают: «Мы там еще?» Короче говоря, клиент запрашивает у сервера данные. Сервер не имеет данных и ждет некоторое время перед отправкой ответа:
- Если что-то всплывает во время ожидания, сервер отправляет его и закрывает запрос;
- Если отправку нечем отправить и достигается максимальное время ожидания, сервер отправляет ответ, что данных нет;
- В обоих случаях клиент открывает следующий запрос на данные;
- Пены, промыть, повторить.
AJAX вызывает работу над протоколом HTTP, означающий, что запросы на один и тот же домен должны быть мультиплексированы по умолчанию. Тем не менее, мы попали несколько проблем, пытаясь заставить, что работать по мере необходимости. Некоторые из подводных камней, которые мы определили с нашим подходом виджетов:
- Заголовки Накладные расходы
Каждый запрос и ответ опроса является полным сообщением HTTP и содержит полный набор заголовков HTTP в обрамлении сообщений. В нашем случае, когда у нас есть небольшие частые сообщения, заголовки на самом деле представляют собой больший процент передаваемых данных. Фактическая полезная полезная нагрузка намного меньше, чем общая передаваемая байт (например, 15 кБ заголовков на 5 КБ данных). - Максимальная задержка
После ответа сервера он больше не может отправлять данные клиенту до тех пор, пока клиент не отправит следующий запрос. В то время как средняя задержка для длительного опроса близка к одному сетевому транзиту, максимальная задержка составляет более трех сетевых транзитов: ответ, запрос, ответ. Однако из-за потери пакетов и ретрансляции максимальная задержка для любого протокола TCP/IP составит более трех сетевых транзитов (можно избежать с помощью трубоукладки HTTP). В то время как в прямом подключении LAN это не большая проблема, она становится одной, пока один находится в движении и переключении сетевых ячеек. В определенной степени, это наблюдается с SSE и WebSockets, но эффект наибольший с опроса. -
Соединение Создание
Хотя этого можно избежать, используя с постоянным ими тихим подключением HTTP для многих запросов опроса, это сложно, соответственно, время все ваши компоненты для опроса в короткие сроки продолжительности, чтобы сохранить соединение жив. В конце концов, в зависимости от ответов сервера, ваши опросы будут десинхронизированы. -
Ухудшение производительности
Долгосрочный клиент опроса (или сервер), наданный, имеет естественную тенденцию к деградации производительности за счет задержки сообщений. Когда это произойдет, события, которые будут отодвинуты к клиенту, будут стоять в очереди. Это действительно зависит от реализации; в нашем случае, мы должны агрегировать все данные, как мы отправляем добавить / удалить / обновить события для наших виджетов. -
Времени ожидания
Длинные запросы опроса должны оставаться невыполненными до тех пор, пока сервер не отправит что-то для отправки клиенту. Это может привести к закрытию соединения прокси-сервером, если оно остается бездельным слишком долго. -
Мультиплексирования
Это может произойти, если ответы происходят в то же время в течение постоянного соединения HTTP/2. Это может быть сложно сделать, так как ответы на опросы не могут быть синхронизированы.
Подробнее о реальных проблемах можно испытать с длиннымопроса можно найти здесь.
Websockets
В качестве первого примера метода нажатия сервера мы рассмотрим WebSockets.
Через MDN:
WebSockets является передовой технологией, которая позволяет открыть интерактивную сессию связи между браузером пользователя и сервером. С помощью этого API вы можете отправлять сообщения на сервер и получать ответы на события, не осняв на сервер для ответа.
Это протокол связи, обеспечивающий полнодупочные каналы связи по одному подключению TCP.
Как HTTP, так и WebSockets расположены на уровне приложения от модели OSI и как таковые зависят от TCP на уровне 4.
- Приложения
- Презентации
- Сессии
- Транспорта
- Сети
- Ссылка на данные
- Физической
RFC 6455 утверждает, что WebSocket «предназначен для работы над портами HTTP 80 и 443, а также для поддержки http прокси и посредников», что делает его совместимым с протоколом HTTP. Для достижения совместимости рукопожатие WebSocket использует заголовок обновления HTTP для изменения с протокола 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 оставляют вас уязвимыми для такого рода атак. -
Изобретая колесо:
С WebSockets, нужно обрабатывать много проблем, которые заботятся в HTTP самостоятельно.
Подробнее о реальных проблемах с WebSockets можно прочитать здесь.
Некоторые хорошие случаи использования для WebSockets являются чаты и многопользовательские игры, в которых преимущества перевешивают вопросы реализации. С их основным преимуществом является дуплексное общение, и нам это не очень нужно, мы должны двигаться дальше.
Влияние
Мы получаем увеличенные эксплуатационные накладные расходы с точки зрения разработки, тестирования и масштабирования; программное обеспечение и это ИТ-инфраструктура с обоими: опросы и WebSockets.
Мы получаем тот же вопрос по мобильным устройствам и сетям с обоими. Аппаратная конструкция этих устройств поддерживает открытое соединение, сохраняя антенну и подключение к сотовой сети в живых. Это приводит к сокращению времени автономной работы, нагрева, а в некоторых случаях, дополнительных сборов за данные.
Но почему у нас все еще есть проблемы с мобильными устройствами?
Рассмотрим, как мобильное устройство по умолчанию подключается к Интернету:
Простое объяснение того, как работает мобильная сеть: Обычно мобильные устройства имеют антенну с низкой мощностью, которая может получать данные из ячейки. Таким образом, как только устройство получает данные от входящего вызова, оно загружает полнодуплексную антенну для того, чтобы установить вызов. Та же антенна используется всякий раз, когда вы хотите сделать звонок или доступ в Интернет (если WiFi не доступен). Полнодуплексная антенна должна установить подключение к сотовой сети и сделать некоторую аутентификацию. Как только соединение установлено, есть некоторая связь между вашим устройством и ячейкой, чтобы сделать наш сетевой запрос. Мы перенаправляемся на внутренний прокси-провайдера мобильной связи, который обрабатывает запросы в Интернете. С тех пор процедура уже известна: она спрашивает DNS, где www.domainname.ext
на самом деле находится, получая URI к ресурсу, и в конечном итоге получить перенаправлены на него.
Этот процесс, как вы могли себе представить, привлекает довольно много заряда батареи. Это причина, почему поставщики мобильных телефонов дают резервное время в несколько дней и время разговора всего за пару часов.
Без Wi-Fi, как WebSockets и опроса требуют полнодуплексантенны работать почти постоянно. Таким образом, мы сталкиваемся с увеличением потребления данных и увеличением мощности рисовать — и в зависимости от устройства — тепло, тоже.
К тому времени, когда все покажется мрачным, похоже, нам придется пересмотреть бизнес-требования для нашего приложения. Мы что-то упустили?
Sse
Через MDN:
«Интерфейс EventSource используется для приема событий, отправленных сервером. Он подключается к серверу через HTTP и получает события в формате текст/поток событий без закрытия соединения.»
Основное отличие опроса в том, что мы получаем только одно соединение и сохраняем поток событий, проходящий через него. Длинный опрос создает новое соединение для каждого тянуть — ergo заголовки накладных расходов и других вопросов, с которыми мы столкнулись там.
Через 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
Каждый EventSource
объект имеет следующие члены:
- URL: набор во время строительства.
- Запрос: изначально является недействительным.
- Время повторного подключения: значение в мс (пользователь-агент-определенное значение).
- Последний идентификатор события: первоначально пустая строка.
- Состояние готового: состояние соединения.
- ПОДКЛЮЧЕНИЕ (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: UniqueIDn');
response.write("data: " + data + 'nn'); // whenever you send two new line characters the message is sent automatically
}
Мы определяем функцию, которая будет обрабатывать ответ:
- Настройка заголовки
- Создание сообщения
- Отправить
Обратите внимание, что вы не видите send()
вызова или push()
метода. Это происходит потому, что стандарт определяет, что сообщение будет отправлено, как только оно получит два nn
символа, как в примере: response.write("data: " + data + 'nn');
. Это немедленно нажмет сообщение к клиенту. Пожалуйста, обратите внимание, что data
должна быть сбежавшая строка и не имеет новых символов линии в конце его.
Строительство сообщений
Как упоминалось ранее, сообщение может содержать несколько свойств:
-
Id
Если значение поля не содержит NULL, установите последний буфер идентификатора событий в значение поля. В противном случае, игнорировать поле. -
Данных
Привизить значение поля к буферу данных, а затем приспособите к буферу данных один символ U’000A LINE FEED (LF). -
Событие
Установите буфер типа событий в значение поля. Это приводит кevent.type
получению пользовательского названия события. -
Повторить
Если значение поля состоит только из цифр ASCII, то интерпретируйте значение поля как целый номер в базовой десятке и установите время повторного подключения потока событий к этому целым числам. В противном случае, игнорировать поле.
Все остальное будет проигнорировано. Мы не можем представить свои поля.
Пример с event
добавлением:
response.write('id: UniqueIDn');
response.write('event: addn');
response.write('retry: 10000n');
response.write("data: " + data + 'nn');
Затем на клиенте, это обрабатывается как 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: }nn
...
Это значительно упрощает то, что мы можем сделать с нашими данными.
Специфические требования к серверу
Во время нашего POC для бэк-энда, мы определили, что она имеет некоторые особенности, которые должны быть рассмотрены, чтобы иметь рабочую реализацию SSE. В лучшем случае вы будете использовать сервер на основе циклов событий, как NodeJS, Kestrel или Twisted. Идея заключается в том, что с решением на основе потоков у вас будет поток на подключение и 1000 соединений и 1000 потоков. С решением цикла событий, вы будете иметь один поток для 1000 соединений.
- Вы можете принимать запросы EventSource только в том случае, если в запросе HTTP говорится, что он может принять тип MIME потока событий;
- Для испускания новых событий необходимо вести список всех подключенных пользователей;
- Вы должны прослушать упавсоединения и удалить их из списка подключенных пользователей;
- Необходимо по желанию поддерживать историю сообщений, чтобы клиенты, повторно подключив, могли догнать пропущенные сообщения.
Он работает как ожидалось и выглядит как магия на первый взгляд. Мы получаем все, что мы хотим для нашего приложения, чтобы работать эффективно. Как и все вещи, которые выглядят слишком хорошо, чтобы быть правдой, мы иногда сталкиваемся с некоторыми проблемами, которые должны быть решены. Тем не менее, они не являются сложными для реализации или обойти:
- Наследие прокси-серверов, как известно, в некоторых случаях, падение HTTP соединения после короткого тайм-аута. Для защиты от таких прокси-серверов, авторы могут включать строку комментариев (один, начиная с ‘:’ характер) каждые 15 секунд или около того.
- Авторы, желающие связать связи источника событий друг с другом или с конкретными документами, ранее обслуживаемыми, могут обнаружить, что полагаться на IP-адреса не работает, так как отдельные клиенты могут иметь несколько IP-адресов (из-за наличия нескольких прокси-серверов) и отдельные IP-адреса могут иметь несколько клиентов (из-за совместного использования прокси-сервера). При его пособничества лучше включить уникальный идентификатор, а затем передать этот идентификатор как часть URL-адреса.
-
Авторы также предупреждают, что http chunking может иметь неожиданные негативные последствия для надежности этого протокола, в частности, если chunking осуществляется другим слоем не знают о требованиях к срокам. Если это проблема, chunking может быть отключен для обслуживания потоков событий.
-
Клиенты, поддерживающие ограничение подключения к серверу HTTP, могут столкнуться с проблемами при открытии нескольких страниц с сайта, если на каждой странице есть EventSource на один и тот же домен. Авторы могут избежать этого, используя относительно сложный механизм использования уникальных доменных имен в одном подключении, или позволяя пользователю включить или отключить функциональность EventSource на основе страницы, или путем обмена одним объектом EventSource с помощью общего работника.
-
Поддержка браузера и Полифилы: Край отстает от этой реализации, но доступна полизаполнение, которое может спасти вас. Тем не менее, наиболее важный случай для SSE сделан для мобильных устройств, где IE / Edge не имеют жизнеспособной доли рынка.
Некоторые из доступных полизаполненй:
Бессвязный толчок и другие функции
Агенты пользователей, работающие в контролируемых средах, например, браузеры на мобильных телефонах, привязанные к определенным операторам, могут разгрузить управление подключением к прокси-серверу в сети. В такой ситуации, агент пользователя для целей соответствия считается включать как программное обеспечение телефонного обеспечения и прокси сети.
Например, браузер на мобильном устройстве, установив соединение, может обнаружить, что он находится в поддерживающей сети, и потребовать, чтобы прокси-сервер в сети взял управление соединением. Сроки такой ситуации могут быть следующими:
- Браузер подключается к удаленному серверу HTTP и запрашивает ресурс, указанный автором в конструкторе EventSource.
- Сервер отправляет случайные сообщения.
- В промежутке между двумя сообщениями браузер обнаруживает, что он простаивает, за исключением сетевой активности, связанной с поддержанием соединения TCP, и решает перейти в режим сна для экономии энергии.
- Браузер отключается от сервера.
- Браузер связывается с службой в сети и просит, чтобы служба, «push прокси», поддерживала соединение.
- Служба «push proxy» связывается с удаленным сервером HTTP и запрашивает ресурс, указанный автором в конструкторе EventSource (возможно, включая
Last-Event-ID
заголовок HTTP и т.д.). - Браузер позволяет мобильному устройству заснуть.
- Сервер отправляет другое сообщение.
- Сервис «push proxy» использует такую технологию, как OMA push, чтобы передать событие на мобильное устройство, которое просыпается только достаточно, чтобы обработать событие, а затем возвращается в спящий режим.
Это может сократить общее использование данных и, следовательно, привести к значительной экономии электроэнергии.
Наряду с реализацией существующего формата API и текстовых/потоковых проводов, определяемого спецификацией и более распределенными способами (как описано выше), могут быть поддержаны форматы обрамления событий, определяемые другими применимыми спецификациями.
Сводка
После долгих и исчерпывающих POC, включая серверные и клиентские реализации, похоже, SSE является ответом на наши проблемы с доставкой данных. Есть некоторые подводные камни с ним, а также, но они оказались тривиальными, чтобы исправить.
Вот как выглядит наша производственная установка в конце:
Мы получаем следующее от NGINX:
- Прокси-сервер ы aPI в разных местах;
- HTTP/2 и все его преимущества, такие как мультиплексирование для соединений;
- Балансировка нагрузки;
- Ssl.
Таким образом, мы управляем доставкой данных и сертификатами в одном месте, а не делаем это на каждой конечной точке по отдельности.
Основные преимущества, которые мы получаем от этого подхода:
- Эффективность данных;
- Упрощенная реализация;
- Он автоматически мультиплексируется по HTTP/2;
- Ограничивает количество подключений для данных о клиенте одним;
- Обеспечивает механизм для сохранения батареи путем разгрузки соединения с прокси.
SSE является не просто жизнеспособной альтернативой другим методам доставки быстрых обновлений; оно смотрит как оно находится в лиге своих когда это прибывает в оптимизациями для мобильных устройств. По сравнению с альтернативами накладных расходов по сравнению с альтернативами нет. С точки зрения реализации сервера, это не сильно отличается от опроса. Для клиента это гораздо проще, чем опрос, поскольку он требует первоначальной подписки и назначения обработчиков событий — очень похоже на то, как WebSockets управляются.
Проверьте демонстрацию кода, если вы хотите получить простую реализацию сервера клиента.
Ресурсы
- «Известные проблемы и лучшие практики для использования длинных опросов и потоковой передачи в двунаправленной HTTP,»IETF (PDF)
- Рекомендация W3C, W3C
- «БудетWebSocket выжить HTTP/2?«, Аллан Денис, Info
- «Stream Обновления с сервером-Sent события,Эрик Бидельман, HTML5 Скалы
- «Приложения push данных с HTML5 SSE,»Даррен Кук, O’Reilly Media
Источник: smashingmagazine.com