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.

1 комментарий:

  1. This makes them a cheap option for complex geometries that may in any other case require multiple of} machines or tool changes utilizing gear corresponding to a standard CNC mill. “First, the dual motion capability of the robotic NURSING SPORTS BRAS to feed and unload two CNC machines will increase manufacturing dramatically. Second, we’re in a position to} cut back the time and price of utilizing companions for sure types of machining to finish manufacturing. And third, it allows us to re-deploy operators to do more skilled work. Now, chilly heading and machining may be accomplished onsite at Optimas’ Wood Dale manufacturing facility, making fastener manufacturing sooner and more seamless for purchasers. This is our shop in Reading, PA, simply exterior of Philadelphia and New York City, and within a day’s drive of a 3rd of the US population.

    ОтветитьУдалить