Реализация 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
Всем Спасибо за внимание!
|
здорово, мне очень понравилось! Подскажите кто нибудь плиз пример серверной части как это можно написать на php? Я так думаю бесконечный цикл придется делать и сокеты? Да и если сделать чат на основе этого SSE и apache php то сколько юзеров он сможет выдержать?
Пример появился там же - https://github.com/Yaffle/EventSource , не знаю как с apache+php, но c cgi php под каждый запрос будет создаваться php процесс, по умолчанию, кол-во php процессов ограничено Nым кол-вом (обычно около 20 ти)
даже если снять ограничение, под каждый процесс создается поток, выделяется память, но в принципе что-то сделать можно
Отправить комментарий
Приветствуются комментарии:Для остальных вопросов и обсуждений есть форум.