Компиляция Nginx модуля под linux
Для поддержки long polling подключений в сервере Nginx, реализован замечательный модуль nginx-push-stream-module. Так как он не входит в официальную поставку, его нужно скачать, настроить и скомпилировать вместе с Nginx.
Перед этим у вас должны быть установлены все необходимые пакеты
apt-get install git apt-get install make apt-get install g++ apt-get install libpcre3 libpcre3-dev libpcrecpp0 libssl-dev zlib1g-dev
Далее нужно скачать сам модуль nginx-push-stream-module, nginx и скомпилировать их вместе.
Клонируем проект из GIT
git clone http://github.com/wandenberg/nginx-push-stream-module.git
Скачиваем и распаковываем последний nginx
NGINX_PUSH_STREAM_MODULE_PATH=$PWD/nginx-push-stream-module wget http://nginx.org/download/nginx-1.2.6.tar.gz tar xzvf nginx-1.2.6.tar.gz
Настраиваем и компилируем nginx вместе с nginx-push-stream-module
cd nginx-1.2.6 ./configure --add-module=../nginx-push-stream-module make make install
Если нет ошибок компиляции, все готово. Проверим, что мы установили именно тот nginx и то, что теперь в нем действительно есть модуль nginx-push-stream-module
check: /usr/local/nginx/sbin/nginx -v test configuration: /usr/local/nginx/sbin/nginx -c $NGINX_PUSH_STREAM_MODULE_PATH/misc/nginx.conf -t
После выполнения этих команд, вы должны увидеть такое:
nginx version: nginx/1.2.6 the configuration file $NGINX_PUSH_STREAM_MODULE_PATH/misc/nginx.conf syntax is ok configuration file $NGINX_PUSH_STREAM_MODULE_PATH/misc/nginx.conf test is successful
Настройкай Nginx для Long Polling подключений
Для настройки поддержки long polling, в конфигурации Nginx, нужно прописать как минимум два контроллера. Первый для подписчиков (тех кто будет получать сообщения), второй для публикации сообщений (тех кто будет посылать сообщения).
Опуская настройку остальных параметров сервера, конфигурационный файл /usr/local/nginx/nginx.conf должен выглядеть так:
... http { ... server { listen 80; server_name stream.example.com; charset utf-8; location /pub { push_stream_publisher admin; set $push_stream_channel_id $arg_id; allow 1.1.1.1 # ip адрес сервера посылающего событие } location ~ /sub/(.*) { push_stream_subscriber long-polling; set $push_stream_channels_path $1; push_stream_last_received_message_tag $arg_tag; push_stream_last_received_message_time $arg_time; push_stream_longpolling_connection_ttl 25s; } } }
В данном примере /pub — адрес для публикации сообщений, его должен видеть только ваш сервер (1.1.1.1), от которого приходят события, /sub — адрес для подписчиков, тех кому будут пересылаться сообщения. Идентификатор, который будет идентифицировать подписчиков, передается после /sub, и принимается как параметр id в /pub.
Об очень важных параметрах push_stream_last_received_message_tag и push_stream_last_received_message_time речь пойдет ниже, когда коснемся javascript.
Пример для понимания работы:
Можно создать несколько подписчиков, вызвав: stream.example.com/sub/1, stream.example.com/sub/2, stream.example.com/sub/3. Каждый из них будет «висеть» на Nginx сервере в течении 25 секунд (push_stream_longpolling_connection_ttl). Если мы вызовем POST запрос на stream.example.com/pub?id=2 и передадим в теле сообщение «Hello», то подписчик «висящий» на /sub/2, получит ответ «Hello». Удобно проверять это в плагине Poster для FireFox.
Создание подписчиков в Javascript
Скорее всего, long polling вам нужно использовать для обновления каких-либо данных в браузере, и для этого вам понадобится написать Javascript клиента.
Я пробовал разные метода, но за эталон выбрал CORS (Cross-origin resource sharing). По сравнению с другими методами имеет следующие преимущества:
- Отлично работает во вех браузерах Chrome, FireFox, Opera, IE 8, 9, 10
- В браузерах не висит иконка загрузки страницы
- Работает на разных доменах (кроссдоменно)
Пускай $ — jQuery, а в переменной subID — уникальное значение для подписчика
var LongPolling = { etag: 0, time: null, init: function () { var $this = this, xhr; if ($this.time === null) { $this.time = $this.dateToUTCString(new Date()); } if (window.XDomainRequest) { // Если IE, запускаем работу чуть позже (из-за бага IE8) setTimeout(function () { $this.poll_IE($this); }, 2000); } else { // Создает CORS объект mcXHR = xhr = new XMLHttpRequest(); xhr.onreadystatechange = xhr.onload = function () { if (4 === xhr.readyState) { // Если пришло сообщение if (200 === xhr.status && xhr.responseText.length > 0) { // Берем Etag и Last-Modified из Header ответа $this.etag = xhr.getResponseHeader('Etag'); $this.time = xhr.getResponseHeader('Last-Modified'); // Вызываем обработчик сообщения $this.action(xhr.responseText); } if (xhr.status > 0) { // Если ничего не пришло повторяем операцию $this.poll($this, xhr); } } }; // Начинаем long polling $this.poll($this, xhr); } }, poll: function ($this, xhr) { var timestamp = (new Date()).getTime(), url = 'http://stream.example.com/sub/' + subID + '?callback=?&v=' + timestamp; // timestamp помогает защитить от кеширования в браузерах xhr.open('GET', url, true); xhr.setRequestHeader("If-None-Match", $this.etag); xhr.setRequestHeader("If-Modified-Since", $this.time); xhr.send(); }, // То же самое что и poll(), только для IE poll_IE: function ($this) { var xhr = new window.XDomainRequest(); var timestamp = (new Date()).getTime(), url = 'http://stream.example.com/sub/' + subID + '?callback=?&v=' + timestamp; xhr.onprogress = function () {}; xhr.onload = function () { $this.action(xhr.responseText); $this.poll_IE($this); }; xhr.onerror = function () { $this.poll_IE($this); }; xhr.open('GET', url, true); xhr.send(); }, action: function (event) { // получили сообщение, и теперь можем что-то обновить ... }, valueToTwoDigits: function (value) { return ((value < 10) ? '0' : '') + value; }, // представление даты в виде UTC dateToUTCString: function () { var time = this.valueToTwoDigits(date.getUTCHours()) + ':' + this.valueToTwoDigits(date.getUTCMinutes()) + ':' + this.valueToTwoDigits(date.getUTCSeconds()); return this.days[date.getUTCDay()] + ', ' + this.valueToTwoDigits(date.getUTCDate()) + ' ' + this.months[date.getUTCMonth()] + ' ' + date.getUTCFullYear() + ' ' + time + ' GMT'; } }
Важно сказать о двух параметрах etag и time.
xhr.setRequestHeader("If-None-Match", $this.etag); xhr.setRequestHeader("If-Modified-Since", $this.time);
Без них long polling работал далеко не всегда и сообщения приходили через раз. Эти два параметра, нужны модулю nginx-push-stream-module, для идентификации сообщений, которые ещё не получил подписчик. Так что для стабильной работы это просто необходимо.
В заключении
Метод описанный в данном топике используется и успешно работает в нашей системе комментирования Cackle. Каждый день у нас порядка 20 000 — 30 000 параллельных подписчиков, и мы не разу не наблюдали каких либо ошибок в доставке сообщений. Для продакшн решения это именно то, что надо.
ссылка на оригинал статьи http://habrahabr.ru/company/cackle/blog/167895/
Добавить комментарий