В настоящее время, благодаря таким инструментам как NodeJS, создание веб-приложения — сущий пустяк. Скачал бинарник, сваял js в 5 строчек кода и можно хвастаться. А если подключить express и добавить ещё 5 строчек, то получим полноценное веб-приложение с роутингом, шаблонами, сессиями и другими прелестями. Так просто, что даже скучно. И стало мне интересно: как обстоят дела у моего старого знакомого С++, с которым уже 5 лет не виделся. В своё время прельстил меня ActionScript и прочий JavaScript, а о добром друге, который не раз выручал, совсем позабыл. В свете недавних статей о Configurable Omnipotent Custom Applications Integrated Network Engine (сокращено Cocaine), попался мне на глаза проект под названием Fastcgi Daemon, на основе которого функционирует HTTP-интерфейс Cocaine. И так, знакомьтесь
То есть
К сожалению, это всё, что вы найдёте в README из официального репозитория.
Больше документации доступно здесь github.com/lmovsesjan/Fastcgi-Daemon/wiki/_pages, но для полноценной работы и её недостаточно. Например, вся установка там занимает одну строку:
Но в официальных репозиториях Ubuntu, да и в каких-либо других (может плохо искал), эти пакеты обнаружить не удалось. В этой статье я решил собрать свои изыскания, связанные с установкой, настройкой и использованием этого инструмента. Все приведённые ниже исходники, а также готовые deb-файлы можно забрать здесь github.com/nickalie/HelloFastCGI
Проект поддерживается для Ubuntu, поэтому все операции ниже я производил на Ubuntu 12.04 64 bit со свежими обновлениями.
Для начала установим все необходимые зависимости. Сделать это можно при помощи следующей команды:
Теперь клонируем репозиторий с Fastcgi Daemon. Тут у нас на выбор 2 варианта:
Я выбрал первый вариант
Переходим в папку со свежескачанным проектом
и запускаем сборку
На выходе получаем готовые к установке deb-файлы, которые находятся в родительской директории. Делаем
и
Также нам понадобится веб-сервер, который умеет работать с FastCGI. Я для своих нужд использую nginx, тоже самое советуют и в документации
Если хочется использовать свежайшую версию этого веб-сервера, то перед этим делаем
На этом с установкой покончено. Переходим к нашему первому приложению.
Создаём файл HelloFastCGI.cpp и помещаем в него следующий код:
Метод, который нас больше всего интересует — handleRequest. Именно он занимается обработкой запроса. Надеюсь из кода понятно, что происходит в этом методе, но на всякий случай поясню. Если в запросе (POST или GET) есть параметр “name”, то выводим текcт “Hello %name%”, иначе “Hello stranger”.
Класс fastcgi::Request отвечает одновременно и за запрос, и за ответ, хотя обычно эта функциональность разделяется на 2 класса или объекта, как, например, в том же NodeJS.
Уже “из коробки” мы можем работать c cookies, выставлять произвольные HTTP-статусы, заголовки и т. п. В общем, нам доступен джентельменский набор для разработки веб-сервисов и веб-приложений. Разве что по умолчанию не реализованы сессии, но они прикручиваются в 2 счёта. Об этом расскажу в следующей раз.
Вернёмся же к нашим баранам. Теперь нам нужно скомпилировать этот класс в shared-библиотеку:
Затем следует подготовить конфигурационный файл HelloFastCGI.conf (важно, чтобы расширение было “conf”):
У нас есть обработчик (handler) запросов, которые приходят на “/hellofastcgi” (например, www.somedomain.com/hellofastcgi). Этим обработчиком является компонент HelloFastCGIComponent, который лежит в модуле MainModule. Точнее в модуле лежит фабрика HelloFastCGIFactory, которая позволяет получить необходимый компонент. В свою очередь MainModule черпает свои ресурсы из недавно скомпилированной libHelloFastCGI.so. Также стоит обратить внимание на содержимое тэга “socket” — это ни что иное как unix-сокет, который нам вскоре понадобиться указывать в настройках nginx. “pidfile” — важен при демонизации FastCGI-Daemon. Его имя должно совпадать с именем conf-файла, различие только в расширениях. Чтобы демон был в состоянии делать start/stop/restart, pid должен лежать именно в “/var/run/fastcgi2/”.
Самое время настроить веб-сервер. Так как всё это я проделываю на свежеустановленном nginx, то, не мудрствуя лукаво, правлю /etc/nginx/sites-available/default. Содержание должно быть примерно следующим:
Перезапускаем nginx
Запускаем наше приложение при помощи
Если всё было сделано правильно, то открыв в браузере localhost/hellofastcgi, вы должны увидеть
Добавим аргумент localhost/hellofastcgi?name=nick и получим
Ура, работает! Надеюсь, у вас тоже.
Однако, сейчас Fastcgi Daemon запускается в консоли как обычное приложение. Где-же обещанная демонизация? Об этом поговорим ниже.
К счастью Fastcgi Daemon на то и Daemon, что его очень легко демонизировать (прошу прощения за тавтологию).
Берём ранее созданный HelloFastCGI.conf, заменяем в нём относительный путь к libHelloFastCGI.so на абсолютный и кладём в /etc/fastcgi2/available.
Теперь можно запускать/останавливать/перезапускать демона привычным способом:
В нашем случае это будет
Чтобы запустить все доступные в /etc/fastcgi2/available приложения используем ключевое слово “all”
Немаловажным также является то, что в случае непредвиденного падения вашего приложения, оно будет автоматически перезапущено.
Любопытства ради решил сравнить производительность Fastcgi Daemon и NodeJS. Для этого набросал аналогичное приложение на JS:
Для чистоты эксперимента настроил proxy_pass в nginx для работы с node.
Тестировал при помощи Apache Bench:
и
Результаты:
Fastcgi Daemon
NodeJS
Стоит упомянуть, что всё это крутится на VirtualBox, которому выделено одно ядро от Core i7.
В итоге получили разницу в полтора раза в пользу Fastcgi Daemon, что, как мне кажется, не так уж и плохо для NodeJS. Вот только потребление процессора у NodeJS доходило до 50%, а памяти — до 45 MБ (5.6 МБ в состоянии покоя). В то время как Fastcgi Daemon отъедал не более 20% процессора и 9.5 МБ RAM (4.5 МБ в состоянии покоя). То есть к ресурсам последний более дружелюбен, что неудивительно. Ясное дело, что сравнение получилось совсем сферическим в вакууме. По-хорошему, надо делать более насыщенный код, подключать БД, запускать оба приложения в многопоточном режиме. Но для начала и этого хватит.
На мой взгляд, очень интересный проект вышел из недр Яндекса. Помимо того, что Fastcgi Daemon значительно упрощает написание веб-приложений на C++, так ещё и содержит в себе необходимые init.d-скрипты для удобного управления уже готовыми приложениями. В следующий раз я опишу создание сервиса для авторизации на основе Fastcgi Daemon с сессиями, БД и шаблонами.
Fastcgi Daemon — Yandex's opensource framework for design highload FastCGI applications on C++.
То есть
Fastcgi Daemon — это фреймворк с открытым исходным кодом, разработанный в Яндексе и предназначенный для создания высоконагруженных FastCGI-приложений на C++.
К сожалению, это всё, что вы найдёте в README из официального репозитория.
Больше документации доступно здесь github.com/lmovsesjan/Fastcgi-Daemon/wiki/_pages, но для полноценной работы и её недостаточно. Например, вся установка там занимает одну строку:
sudo apt-get install fastcgi-daemon2-init libfastcgi-daemon2-dev libfastcgi2-syslog
Но в официальных репозиториях Ubuntu, да и в каких-либо других (может плохо искал), эти пакеты обнаружить не удалось. В этой статье я решил собрать свои изыскания, связанные с установкой, настройкой и использованием этого инструмента. Все приведённые ниже исходники, а также готовые deb-файлы можно забрать здесь github.com/nickalie/HelloFastCGI
Установка
Проект поддерживается для Ubuntu, поэтому все операции ниже я производил на Ubuntu 12.04 64 bit со свежими обновлениями.
Для начала установим все необходимые зависимости. Сделать это можно при помощи следующей команды:
sudo apt-get install -y build-essential git debhelper automake1.9 autotools-dev libboost-dev libboost-thread-dev libfcgi-dev libxml2-dev libboost-regex-dev libtool libssl-dev autoconf-archive
Теперь клонируем репозиторий с Fastcgi Daemon. Тут у нас на выбор 2 варианта:
- github.com/golubtsov/Fastcgi-Daemon — последний коммит год назад. Именно этот репозиторий фигурирует в документации к Cocaine
- github.com/lmovsesjan/Fastcgi-Daemon — выглядит более-менее живым, последний коммит 21 день назад. Содержит внятное описание API фреймворка.
Я выбрал первый вариант
git clone https://github.com/golubtsov/Fastcgi-Daemon.git
Переходим в папку со свежескачанным проектом
cd Fastcgi-Daemon
и запускаем сборку
dpkg-buildpackage -rfakeroot
На выходе получаем готовые к установке deb-файлы, которые находятся в родительской директории. Делаем
cd ..
и
sudo dpkg -i ./libfastcgi-daemon2-dev_2.10-13_amd64.deb \
./libfastcgi-daemon2_2.10-13_amd64.deb \
./fastcgi-daemon2-init_2.10-13_amd64.deb \
./fastcgi-daemon2_2.10-13_amd64.deb \
./libfastcgi2-syslog_2.10-13_amd64.deb
Также нам понадобится веб-сервер, который умеет работать с FastCGI. Я для своих нужд использую nginx, тоже самое советуют и в документации
sudo apt-get install nginx
Если хочется использовать свежайшую версию этого веб-сервера, то перед этим делаем
sudo add-apt-repository ppa:nginx/stable && sudo apt-get update
На этом с установкой покончено. Переходим к нашему первому приложению.
Приложение
Создаём файл HelloFastCGI.cpp и помещаем в него следующий код:
#include <fastcgi2/component.h>
#include <fastcgi2/component_factory.h>
#include <fastcgi2/handler.h>
#include <fastcgi2/request.h>
#include <iostream>
#include <sstream>
class HelloFastCGI : virtual public fastcgi::Component, virtual public fastcgi::Handler
{
public:
HelloFastCGI(fastcgi::ComponentContext *context) :
fastcgi::Component(context)
{
}
virtual void onLoad()
{
}
virtual void onUnload()
{
}
virtual void handleRequest(fastcgi::Request *request, fastcgi::HandlerContext *context)
{
request->setContentType("text/plain");
std::stringbuf buffer("Hello " + (request->hasArg("name") ? request->getArg("name") : "stranger"));
request->write(&buffer);
}
};
FCGIDAEMON_REGISTER_FACTORIES_BEGIN()
FCGIDAEMON_ADD_DEFAULT_FACTORY("HelloFastCGIFactory", HelloFastCGI)
FCGIDAEMON_REGISTER_FACTORIES_END()
Метод, который нас больше всего интересует — handleRequest. Именно он занимается обработкой запроса. Надеюсь из кода понятно, что происходит в этом методе, но на всякий случай поясню. Если в запросе (POST или GET) есть параметр “name”, то выводим текcт “Hello %name%”, иначе “Hello stranger”.
Класс fastcgi::Request отвечает одновременно и за запрос, и за ответ, хотя обычно эта функциональность разделяется на 2 класса или объекта, как, например, в том же NodeJS.
Уже “из коробки” мы можем работать c cookies, выставлять произвольные HTTP-статусы, заголовки и т. п. В общем, нам доступен джентельменский набор для разработки веб-сервисов и веб-приложений. Разве что по умолчанию не реализованы сессии, но они прикручиваются в 2 счёта. Об этом расскажу в следующей раз.
Вернёмся же к нашим баранам. Теперь нам нужно скомпилировать этот класс в shared-библиотеку:
g++ HelloFastCGI.cpp -O2 -fPIC -lfastcgi-daemon2 -shared -o libHelloFastCGI.so
Затем следует подготовить конфигурационный файл HelloFastCGI.conf (важно, чтобы расширение было “conf”):
<?xml version="1.0"?>
<fastcgi xmlns:xi="http://www.w3.org/2001/XInclude">
<pools>
<pool name="main" threads="1" queue="5000"/>
</pools>
<handlers>
<handler pool="main" url="/hellofascgi">
<component name="HelloFastCGIComponent"/>
</handler>
</handlers>
<components>
<component name="HelloFastCGIComponent" type="MainModule:HelloFastCGIFactory"/>
<component name="daemon-logger" type="logger:logger">
<level>INFO</level>
<ident>hellofastcgi</ident>
</component>
</components>
<modules>
<module name="MainModule" path="./libHelloFastCGI.so"/>
<module name="logger" path="/usr/lib/fastcgi2/fastcgi2-syslog.so"/>
</modules>
<daemon>
<logger component="daemon-logger"/>
<endpoint>
<backlog>128</backlog>
<socket>/tmp/fastcgi_daemon.sock</socket>
<threads>1</threads>
</endpoint>
<pidfile>/var/run/fastcgi2/HelloFastCGI.pid</pidfile>
<monitor_port>20012</monitor_port>
</daemon>
</fastcgi>
У нас есть обработчик (handler) запросов, которые приходят на “/hellofastcgi” (например, www.somedomain.com/hellofastcgi). Этим обработчиком является компонент HelloFastCGIComponent, который лежит в модуле MainModule. Точнее в модуле лежит фабрика HelloFastCGIFactory, которая позволяет получить необходимый компонент. В свою очередь MainModule черпает свои ресурсы из недавно скомпилированной libHelloFastCGI.so. Также стоит обратить внимание на содержимое тэга “socket” — это ни что иное как unix-сокет, который нам вскоре понадобиться указывать в настройках nginx. “pidfile” — важен при демонизации FastCGI-Daemon. Его имя должно совпадать с именем conf-файла, различие только в расширениях. Чтобы демон был в состоянии делать start/stop/restart, pid должен лежать именно в “/var/run/fastcgi2/”.
Самое время настроить веб-сервер. Так как всё это я проделываю на свежеустановленном nginx, то, не мудрствуя лукаво, правлю /etc/nginx/sites-available/default. Содержание должно быть примерно следующим:
server {
listen 80;
location / {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
fastcgi_pass unix:/tmp/fastcgi_daemon.sock;
}
}
Перезапускаем nginx
sudo service nginx restart
Запускаем наше приложение при помощи
fastcgi-daemon2 --config=HelloFastCGI.conf
Если всё было сделано правильно, то открыв в браузере localhost/hellofastcgi, вы должны увидеть
Hello stranger
Добавим аргумент localhost/hellofastcgi?name=nick и получим
Hello nick
Ура, работает! Надеюсь, у вас тоже.
Однако, сейчас Fastcgi Daemon запускается в консоли как обычное приложение. Где-же обещанная демонизация? Об этом поговорим ниже.
Настройка daemon
К счастью Fastcgi Daemon на то и Daemon, что его очень легко демонизировать (прошу прощения за тавтологию).
Берём ранее созданный HelloFastCGI.conf, заменяем в нём относительный путь к libHelloFastCGI.so на абсолютный и кладём в /etc/fastcgi2/available.
Теперь можно запускать/останавливать/перезапускать демона привычным способом:
sudo service fascgi-daemon2 start/stop/restart <appname>/all
В нашем случае это будет
sudo service fastcgi-daemon2 start HelloFastCGI
Чтобы запустить все доступные в /etc/fastcgi2/available приложения используем ключевое слово “all”
sudo service fastcgi-daemon2 start all
Немаловажным также является то, что в случае непредвиденного падения вашего приложения, оно будет автоматически перезапущено.
Бенчмарки
Любопытства ради решил сравнить производительность Fastcgi Daemon и NodeJS. Для этого набросал аналогичное приложение на JS:
var http = require('http');
var url = require('url');
http.createServer(function (req, res)
{
res.writeHead(200, {'Content-Type': 'text/plain'});
var query = url.parse(req.url, true).query;
res.end('Hello ' + (query.name ? query.name : 'stranger'));
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
Для чистоты эксперимента настроил proxy_pass в nginx для работы с node.
Тестировал при помощи Apache Bench:
ab -c 100 -n 20000 http://IPorURL/hellofastcgi?name=Nikolay
и
ab -c 100 -n 20000 http://IPorURL/?name=Nikolay
Результаты:
Fastcgi Daemon
Concurrency Level: 100
Time taken for tests: 15.181 seconds
Complete requests: 20000
Failed requests: 0
Write errors: 0
Non-2xx responses: 20000
Total transferred: 6460000 bytes
HTML transferred: 3440000 bytes
Requests per second: 1317.45 [#/sec] (mean)
Time per request: 75.904 [ms] (mean)
Time per request: 0.759 [ms] (mean, across all concurrent requests)
Transfer rate: 415.56 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.5 0 4
Processing: 12 75 31.2 68 474
Waiting: 9 73 31.4 66 471
Total: 12 76 31.3 68 475
Percentage of the requests served within a certain time (ms)
50% 68
66% 80
75% 85
80% 88
90% 96
95% 106
98% 114
99% 125
100% 475 (longest request)
NodeJS
Concurrency Level: 100
Time taken for tests: 23.038 seconds
Complete requests: 20000
Failed requests: 0
Write errors: 0
Total transferred: 2700000 bytes
HTML transferred: 260000 bytes
Requests per second: 868.12 [#/sec] (mean)
Time per request: 115.192 [ms] (mean)
Time per request: 1.152 [ms] (mean, across all concurrent requests)
Transfer rate: 114.45 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.6 0 52
Processing: 39 114 21.7 109 306
Waiting: 28 112 21.5 107 305
Total: 40 115 21.7 109 306
Percentage of the requests served within a certain time (ms)
50% 109
66% 117
75% 125
80% 130
90% 145
95% 155
98% 168
99% 186
100% 306 (longest request)
Стоит упомянуть, что всё это крутится на VirtualBox, которому выделено одно ядро от Core i7.
В итоге получили разницу в полтора раза в пользу Fastcgi Daemon, что, как мне кажется, не так уж и плохо для NodeJS. Вот только потребление процессора у NodeJS доходило до 50%, а памяти — до 45 MБ (5.6 МБ в состоянии покоя). В то время как Fastcgi Daemon отъедал не более 20% процессора и 9.5 МБ RAM (4.5 МБ в состоянии покоя). То есть к ресурсам последний более дружелюбен, что неудивительно. Ясное дело, что сравнение получилось совсем сферическим в вакууме. По-хорошему, надо делать более насыщенный код, подключать БД, запускать оба приложения в многопоточном режиме. Но для начала и этого хватит.
Вместо заключения
На мой взгляд, очень интересный проект вышел из недр Яндекса. Помимо того, что Fastcgi Daemon значительно упрощает написание веб-приложений на C++, так ещё и содержит в себе необходимые init.d-скрипты для удобного управления уже готовыми приложениями. В следующий раз я опишу создание сервиса для авторизации на основе Fastcgi Daemon с сессиями, БД и шаблонами.