Javascript.RU

Реализация HTTP server push с помощью Server-Sent Events

На эту тему было уже много статей, но раскрыта далеко не вся правда. Для тех, кто пропустил - читайте Создание приложений реального времени с помощью Server-Sent Events .

Как же работает Server-Sent-Events?

Простой пример:

    (new EventSource('/events')).addEventListener('message', function (e) {
      document.getElementById('body').innerHTML += e.data + '';
    }, false);

Браузер устанавливает http-соединение, и для каждого сообщения с сервера срабатывает событие, в обработчике которого мы можем получить текст сообщения. При этом вовсе не обязательно с серверной стороны разрывать соединение, как этого требует XMLHTTPRequest. Поэтому по единожды установленному соединению мы можем получать сообщения с сервера.

Еще одним плюсом Server-sent-events является то, что последняя спецификация теперь поддерживается браузерами Chrome, Opera 11+, заявлена поддержка в Firefox Aurora, а для браузеров IE8+, Firefox 3.5+ можно реализовать polyfill на javascript, для более старых браузеров полифил может использовать long-polling. Таким образом, EventSource лучше поддерживается, нежели WebSockets. (Нативную поддержку смотрим здесь - http://caniuse.com/#search=eventsource)

При этом для работы полифила с серверной стороны не требуется подключение каких-то библиотек, достаточно реализовать работу с Server-sent-events, как этого требует стандарт, а не писать код под каждый ajax-транспорт (как это сделано в socket.io, что сильно усложняет ее, больше транспортов - больше тонкостей)

И так, IE8 поддерживает XDomainRequest, который может работать по технологии server-push, т.е. мы можем также получать сообщения, не прерывая http-соединения.К сожалению, XDomainRequest требует паддинг - 2 килобайта в начале тела ответа, но это не проблема. Также, XDomainRequest не поддерживает установку заголовков запроса, поэтому Last-Event-ID придется передавать в теле запроса (POST).

Подробнее об XDomainRequest и его возможностях читать тут - http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx?PageIndex=1 )

Браузер Firefox позволяет обрабатывать сообщения с сервера по мере их поступления через стандартный XMLHttpRequest (см. статью http://javascript.ru/ajax/comet/xmlhttprequest-interactive)
Поэтому он также поддерживает "server-push".
Для остальных же браузеров, можно организовать получение сообщений по схеме long-polling с помощью XMLHttpRequest (т.е. с сервера нам необходимо для таких браузеров разрывать соединение после каждого отправленного сообщения).

Итак, готовый EventSource для браузеров, не имеющих его нативной поддержки - https://github.com/Yaffle/polyfills .
Зависимостей от библиотек - нет, все что нужно для работы:
<script type="text/javascript" src="eventsource.js"></script>

Браузеры ограничивают кол-во одновременных соединений, что при открытии сайта в нескольких вкладках браузера может вызывать проблемы. Конечно, в современных браузеров число соединений ограничено минимум шестью, но если каждая вкладка с вашим сайтом использует 2 или более длинных http-соединения, то этот предел быстро достигается.

Для решения этой проблемы как нельзя лучше подходит SharedWorker, создав SharedWorker мы можем создать внутри него EventSource и рассылать все события с объекта EventSource всем подключившимся скриптам:

Пример, sharedworker.js:

var es = new self.EventSource('events'),
  history = [];
es.addEventListener('message', function (e) {
  history.push(e.data);
}, false);

self.onconnect = function onConnect(event) {
  var port = event.ports[0];

  // отсылаем все полученные ранее сообщения
  history.forEach(function (data) {
    port.postMessage(data);
  });

  
  es.addEventListener('message', function (e) {
    port.postMessage(e.data);
  }, false);
}

Но, к сожалению, SharedWorker поддерживается только браузерами Opera 10.6+ и Chrome(Safari).
При этом Opera не имеет объекта EventSource "внутри" SharedWorker (т.е. в SharedWorkerGlobalScope).
Поэтому данные метод избежания нескольких соединений применим только для Webkit браузеров.
Для всех остальных браузеров нам придется завести отдельный домен.
Т.к. EventSource не поддерживает кросс-доменных запросов, я предлогаю подключать через iframe html-страницу, находящуюся на домене для EventSource, которая будет "запускать" EventSource и передавать сообщения через "postMessage" (недавно была статья на эту тему - http://habrahabr.ru/blogs/javascript/120336/, в которой описано использование библиотеки easyxdm, но "window.postMessage" поддерживается достаточно хорошо современными браузерами (http://caniuse.com/#search=postmessage).

Last-Event-ID
Когда Соединение с сервером разрывается (либо сервер закрыл соединение, либо произошла какая-то ошибка сети), EventSource выполняет повторное подключение через определенное время(это время можно контролировать).При этом новое подключение будет содержать в заголовке Last-Event-ID - идентификатор последнего полученного сообщения.

Рассмотрим пример ответа сервера - event stream:


retry: 1000\n
id: 123
data: hello world\n\n

Поле `retry` указывает серверу через какое кол-во милисекунд выполнять переподключение в случае разрыва соединения Поле `id` будет передано в заголовке Last-Event-ID при переподключении.Поэтому с серверной стороны мы можем легко определить идентификатор последнего полученного пользователем сообщения и передать все накопившиемся после него. Пользователь получитвсе сообщения, ничего не пропустив.

Для простоты в примере, отправка сообщения происходит методом GET без аутентификации и защиты от CSRF.
Время в чате - местное для сервера, сорри.

Пример чата:
http://hostel6.ru:8002
Исходники чата можно скачать тут:

https://github.com/Yaffle/polyfills

Всем Спасибо за внимание!

+1

Автор: simple, дата: 20 сентября, 2011 - 20:40
#permalink

здорово, мне очень понравилось! Подскажите кто нибудь плиз пример серверной части как это можно написать на php? Я так думаю бесконечный цикл придется делать и сокеты? Да и если сделать чат на основе этого SSE и apache php то сколько юзеров он сможет выдержать?


Автор: Гость (не зарегистрирован), дата: 25 сентября, 2011 - 18:38
#permalink

Пример появился там же - https://github.com/Yaffle/EventSource , не знаю как с apache+php, но c cgi php под каждый запрос будет создаваться php процесс, по умолчанию, кол-во php процессов ограничено Nым кол-вом (обычно около 20 ти)

даже если снять ограничение, под каждый процесс создается поток, выделяется память, но в принципе что-то сделать можно


Отправить комментарий

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
3 + 3 =
Введите результат. Например, для 1+3, введите 4.
 
Поиск по сайту
Другие записи этого автора
Больше записей нет. Прокомментируйте эту запись - может быть, тогда он что-нибудь еще хорошее напишет ;)
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Популярные таги
Последние комментарии
Последние темы на форуме
Forum