Comet. Apache + php + Nodejs + Socket.io. Realtime fullduplex web application
Классическая модель интернета - запрос - ответ не позволяет строить полноценные приложения, в которых сервер может отсылать данные клиентам в браузер. Однако существует множество технологий позволяющих тем или иным путем симулировать или добиться настоящей обратной связи, к примеру для построения чата с моментальным откликом на каком-либо веб ресурсе. В статье приведен пример как обычный сайт, использующий в качестве сервера - Apache, можно наполнить динамикой используя современные технологии протокола Websocket, библиотеку js socket.io и серверное приложение, написанное на Nodejs исползуемого Apache'ем в связке с Curl.
Краткая теория как это работает:
Известно что начиная с версии прокола http 1.0 браузеры при отправке запросов сохраняют TCP туннель, и используют его снова для отправки нового запроса, поскольку TCP является протоколом транспортного уровня он предоставляет возможность серверу послать обратный запрос, к сожалению http протокол данной возможности не предоставляет, но сам браузер - способен принять ответ по TCP туннелю и так же как в случае с ajax передать ответь в js функцию которая решит что с этим ответом делать.
Отсюда следует - что для того чтобы обеспечить возможность серверу отсылать в браузер данные нужно воспользоваться API бразуера с помощью js - в этом нам поможет библиотека socket.io, а для того чтобы сервер сумел отправить нам данные - там должен находиться иной сервер (отличный от apache) - способный использовать протокол отличный от http - а именно web socket - для примера был выбран движок Nodejs и написанное на нем серверное приложение, которое будет принимать запросы от браузеров, поддерживать с ними постоянные связи, а при поступлении новых данных на сервер apach'a, отсылать подключенным клиентам в браузер свежую информацию помощью приложения nodejs.
Так же в начале упоминалось про apache - чтобы не переписывать на nodejs весь старый функционал сайта, а просто добавить новую фичу типа чата (пример будет простым, просто описывающим принцип работы) - мы будем использовать apache и когда на сервер будет приходить информация - например новый пост - будет срабатывать Curl - который обычным http запросом с постом будет слать данные на nodejs приложение, которое в свою очередь будет обновлять клиентам данные:
Итак начнем: вначале на сервер apach'а приходит запрос и он возвращает страничку, на которой вставлен js скрипт:
// Подключаем библиотеку для установления
// постоянного конекта с nodejs приложеним.
<script src="socket.io.js"></script>
<script>
var sw='run';
// Конектимся на локальный сервер по 8080 порту.
var socket = io.connect('http://localhost:8080');
// Функция которая сработает когда нам по туннелю
// отправят данные как и в случае с ajax.
socket.on('stream', function (data) {
// Для наглядности вставим ответ внутрь враппера.
document.getElementById('number').innerHTML = data.n;
});
function stop_timer() {
if (sw == 'run') {
// Шлем данные на nodejs приложение по клику.
socket.emit('action', {todo: 'stop'});
sw='stop';
}
else {
// Шлем данные на nodejs приложение по клику.
socket.emit('action', { todo: 'run'});
}
}
</script>
Magic Page
<div style="border:1px solid #ccc" id="number"> </div>
<a href="#" onclick="stop_timer();return false;">Action</a>
Вышеописанный скрипт устанавливает постоянный TCP туннель через Websocket протокол со нижеописанным nodejs приложением написанным на javascript с использованием нескольких обычных модулей\расширений, которые можно установить с помощью утилиты npm:
Nodejs приложение биндит сокет по 8080 порту, и когда бразуер устанавливает с ним сообщение, он единожды биндит другой сокет на другой порт - 8300 (поднимает http сервер), и когда на вторичный адрес приходит запрос отдает всем участникам (пользователям которые установили постоянные соединения) какие-то данные.
// Подключаем библиотеку и сразу же поднимаем сервер на 8080 порт.
var io = require('...path/node_modules/socket.io').listen(8080);
// Load the http module to create an http server.
var http = require('http');
var server = undefined;
io.sockets.on('connection', function (socket) {
// Счетчик для примера.
t = 1;
// Создаем HTTP server если он не был создан.
if (typeof(server) == 'undefined') {
server = http.createServer(function (request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
// На запрос вызываем метод библиотеки socket.io
// чтобы отослать данные подключенным постоянно клиентам.
socket.broadcast.emit('stream', {n:t = t+1});
// Выводим сообщение.
response.end("Hello World\n");
});
}
// Биндим Http server на 8300 порт, IP defaults to 127.0.0.1
server.listen(8300);
// Отсылаем всем клиентам какие-то данные при
// подключении нового клиента.
socket.broadcast.emit('stream', {n:'c'});
socket.on('action', function (data) {
if (data.todo=='stop') {
// Что-нибудь сделать на action stop
}
else if (data.todo = 'run') {
// Что-нибудь сделать на action run
}
});
});
Собственно все уже работает, остается из apacha на php вызвать к примеру с помощью curl http запрос на такой-то порт (8300) такой-то адрес (указанный в nodejs приложении) и тогда nodejs примет его и передаст данные на клиентов.