25 декабря 2013 г.

Tronode.js - часть 2 - подготовка серверной части браузерной игры на node.js

В этом посте я расскажу о том, как я создавал веб-приложение на node.js с WebSocket через Socket.IO. Работу с node.js опишу подробно. Сама веб-приложение - это многопользовательская браузерная игра клон Трона. Я назвал её Tronode.js.


Описание процесса построения клиентской части можно прочитать здесь в соседнем посте, а процесс написания игровой логики в следующем.

Введение в node.js

Node.js - платформа, которая позволяет запускать серверные приложения, написанные на языке javascript. Это означает, что на языке js вы можете открыть порт для прослушивания, например, 80 и отвечать на http запросы с применением различных обработок. В сравнении с php ему не нужен apache или nginx. Все обработки запросов могут происходить в рамках одного приложения. Node.js не ограничен созданием только веб-приложений, но в рамках этой статьи нас интересует только это.

Пакетный менеджер npm

Платформа имеет большое количество модулей. Для их установки используется пакетный менеджер npm. Node.js и npm кроссплатформенные. Но на Windows есть удобный установщик, а, например, на Debian 7 мне пришлось собирать его самому, что впрочем заняло немного времени. Модули для node.js можно устанавливать глобально для системы - для этого используется флаг -g, либо только для проекта. При локальной установке в корне проекта создается директория node_modules, где хранятся все модули для проекта. При развертывание проекта в другом окружении удобно использовать специальный файл package.json, который располагается в корне проекта и содержит описание приложения, в том числе и список модулей, которые требуются для этого приложения, так называемые зависимости. Если такой существует, то командой
npm install
будут скачены и установлены все необходимые модули нужных версий.

Framework Express

Как у любой развитой платформы для веб-приложения, у node.js есть фреймворки для упрощения разработки. Один из таких фреймворков - express. Основное его назначение - это создание веб-приложений. С его помощью можно быстро создать приложение с использованием маршрутизации запросов, шаблонизаторов отображений, работы с сессиями, предобработки css и некоторые другие вещи.

Руководство по express рекомендует устанавливать модуль глобально:
npm install -g express
В этом случае станет доступна утилита express для работы с проектом. Создадим с её помощью веб-приложение с поддержкой сессий, шаблонизатором EJS и Stylus для написания css-правил:
express --sessions --css stylus --ejs tronode-js

Теперь переходим в директорию проекта и устанавливаем зависимости из созданного express'ом файла package.json:
cd tronode.js
npm install

Простой запуск нашего веб-приложения выполняется командой
node app.js
По умолчанию express слушает 3000 порт, поэтому для того, чтобы убедится, что базовое веб-приложение работает, нужно открыть в браузере http://localhost:3000.

При редактировании серверной части нужно перезапускать приложения. Для автоматизации этого процесса можно использовать модуль nodemon:
npm install -g nodemon
nodemon app.js
Он будет отслеживать изменения файлов и каждый раз перезапускать приложение.

Немного о Stylus. С его помощью можно писать css правила в файлах с расширением .styl без фигурных скобок и точек с запятой в конце каждой строки, а так же использовать переменные и некоторые другие удобства. При каждом запуске stylus сам создаст классический .css-файл.

Работа с Socket.IO

Теперь добавим в package.json в раздел зависимостей строку "socket.io": "*". Мой файл выглядит так:
{
  "name": "tronode.js",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.4.6",
    "ejs": "*",
    "stylus": "*",
    "socket.io": "*"
  }
}

Выполним npm install для установки модуля socket.io. Для того, чтобы веб-приложение начало принимать webscoket-соединения, нужно изменить app.js:
вместо
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
нужно
var server = http.createServer(app);
var io = require('socket.io').listen(server);
server.listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Теперь можно подключить пару обработчиков для тестирования (после var io и перед server.listen):
io.sockets.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});


Здесь первым аргументов для функции .on идёт название события, например, 'connection' - стандартное событие при подключении клиента, а дальше 'my other event' - пример любого другого названия события, отправленного клиентом. Второй аргумент функции .on - функция обработчик, которая имеет доступ к данным (если это не стандартное событие) и к сокету (клиенту), который породил событие.
Для отправки события в другую сторону нужно вызвать метод emit, где первым аргументом идет название события, а вторым - объект с данными.

Теперь поработаем над клиентской частью.
Шаблон, который используется для стартовой страница находится в view/index.ejs. Добавим в секцию head подключение клиентской части sockets.io и скрипт для работы с сокетами. Для подключения sockets.io на стороне пользователя нам нужна js-библиотека. Для меня найти её оказалось не так просто и я скачал её с какого-то готового проекта, положил себе в public/javascripts/socket.io.min.js и подключил <script src="/javascripts/socket.io.min.js"></script>
Но оказывается, серверный socket.io при запущенном приложении генерирует на лету такую библиотеку. Для этого нужно подключать её так
<script src="/socket.io/socket.io.js"></script>
И сам код для тестирования:
<script>
  var socket = io.connect('http://localhost:3000');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>
Обратите внимание на адрес подключения. Порт должен совпадать с тем, на каком запущен express. Можно даже через шаблон передать значение серверной переменной port, чтобы удостовериться в их равенстве. Запустите серверное приложение, откройте в браузере консоль и перейдите на http://localhost:3000. Вы должны увидеть обмен сообщениями.

Развертывание на рабочее окружение

Развертывание node.js приложения мне показалось самым простым по сравнению с php и python, но оно имеет свои особенности. Для начала определим, где будет располагаться директория проекта. Я её поместил в /var/www/tronode.js. Теперь установим node.js и npm. Как я уже писал, для Debian 7 я делал сборку по этой инструкции.

Исходный код проекта я храню в bitbucket с использованием Mercurial. Директорию с зависимостями node_modules я предварительно исключил из hg. Bitbucket позволяет добавить deploy-ключи, поэтому deploy-script у меня состоит из вытягивания из репозитория, установке зависимостей и запуска/перезапуска приложения. Для перезапуска на рабочем окружении я использую модуль forever, который сам следит за тем, чтобы приложение было всегда запущено.

Веб-приложение у нас занимает отдельный порт. Если мы имеем другие веб-проекты на том же сервере, то там придется решать, какому приложению отдавать 80 порт. У меня его занял nginx, а 8080 занял apache. Я решил использовать тот же порт 3000, что и на тестовом окружении. Nginx у меня не поддерживает проксирование websocket, зато можно избавится от необходимости приписывать порт к адресу проекта. Для этого я использую nginx как прокси для поддомена. С этой целью я создал и подключил файл конфигурации:

server {
       listen 80;
       server_name tronode.livelevel.net;
       location / {
             proxy_pass         http://127.0.0.1:3000/;
             proxy_redirect     off;
             proxy_set_header   Host             $host;
             proxy_set_header   X-Real-IP        $remote_addr;
             proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
             #proxy_http_version 1.1;
             #proxy_set_header Upgrade $http_upgrade;
             #proxy_set_header Connection $connection_upgrade;

        }


       # Static files location
       location ~* ^.+.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe$
             root   /var/www/tronode.js/public;
             expires 10d;
       }
}

Кроме того в переменные окружения стоит добавить NODE_ENV=production.

Бесплатный хостинг Heroku

Heroku позволяет бесплатно хостить на одной виртуальной единице приложение на node.js
В руководстве описано как создавать и загружать приложения, но я продублирую информацию, чтобы картина была целостной. Прежде всего для работы с Heroku нам нужна их утилита из набора Heroku Toolbelt. Набор так же установит ssh и git. Heroku работает только с git, но это не проблема, так как мы можем использовать hg для обычно работы, а git только для деплоя. Главное, добавить служебные директории систем контроля версий в список исключения друг друга. Для этого нужно смотреть информацию о gitignore и hgignore.

После установки набора нужно выполнить
heroku login
Вводим адрес электронной почты и пароль, указанные при регистрации. Будут вопросы о ssh-ключах, соглашаемся. Если всё успешно, то займемся приложением.

Если у вас не git-проект, то его нужно инициализировать
git init
а затем создать приложение (при этом в git будет добавлен алиас адреса для heroku):
heroku apps:create my-name-of-app

Так как у нас приложение с websocket, то нужно включить его поддержку в heroku:
heroku labs:enable websockets -a my-name-of-app
Затем добавим все файлы в git, зафиксируем и вытолкнем в heroku:
git add .
git commit -m "first commit"
git push heroku master
heroku open
Если всё прошло успешно, то в браузере будет открыто ваше приложение. Но следует помнить, что heroku переопределяет переменную окружения PORT, а значит express будет запущен не на порту 3000 (иначе он вообще запущен не будет). Для адреса подключения к websocket следует использовать тот же адрес, что и приложения, но вместо протокола http// использовать ws://
var host = location.origin.replace(/^http/, 'ws');

С первого раза моё приложение работало, но для транспорта socket.io вместо websocket выбирал xhr-polling, что гораздо медленнее. Я не смог выяснить почему, но позже я попробовал подключится с другого компьютера к моему приложению на Heroku и там уже был нормальный транспорт websocket.

Комментариев нет:

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