… или как перейти с PHP + JavaScript на JavaScript + JavaScript
Идея реализовать проект на сервер-сайд JavaScript была уже давно. Проблема была в отсутствии подходящего серверного программного обеспечения. Существующие открытые проекты не устраивали по разным причинам. Устанавливать дополнительный модуль для Apache было не самой хорошей идеей, потому что производительность и оптимизация использования памяти при этом были бы не на высоте. С помощью jslibs можно настроить FastCGI, но очень не хотелось оставлять ни малейших шансов «502 Bad Gateway», проект ngx_http_js_module так и остался в зачаточной стадии, а ngxv8 недостаточно развит для реализации реальных приложений. Поэтому я решил сделать собственную реализацию серверного javascript. Причем постараться сразу запрограммировать всю базовую функциональность, чтобы можно было ее тестировать в условиях, близких к реальности.
В качестве основного веб-сервера было решено использовать nginx, в качестве «движка» javascript — TraceMonkey (javascript-движок из Mozilla Firefox, бывший SpiderMonkey), и написать модуль для nginx, который бы ��х «склеил». Ничего сложного, на первый взгляд, но очень хотелось иметь определенную функциональность (и это получилось!), чтобы можно было нормально работать дальше. Большинство идей заимствованы, кстати, из PHP.
- Корректная работа в multi-thread условиях
- Возможность выполнять скрипт, указанный в URL, а не настраивать отдельно скрипт-обработчик и функцию-обработчик для каждого location
- Возможность вызывать include(), sleep(), alert() из скрипта, использовать __FILE__ и __LINE__
- Ограничение памяти, выделяемой каждому скрипту, и времени работы скрипта
- Защита открываемых скриптом файлов, указав в настройках список разрешенных папок. Примерно как open_basedir в PHP
- Автоматический разбор данных запроса (параметров GET, POST, и, конечно же, cookies), чтобы не писать обработку данных на javascript
- Поддержка запросов application/x-www-form-urlencoded и multipart/form-data
- Поддержка basic-авторизации
- Работа с базами данных (в первую очередь, MySQL и SQLite)
- Работа с файловой системой: чтение и запись файлов, проверка существования файлов, и т.п.
- Кэширование байт-кода скриптов, как, например, в eAccelerator
От слов — к делу! Как скомпилировать и настроить, как протестировать и сравнить...
Глубоко в детали сборки не вдаюсь, иначе текст получится неимоверных размеров. Пользоват��ли, имеющие опыт «сборки» программ под Linux, будут чувствовать себя вполне комфортно, а для всех остальных могу предложить бинарную сборку и возможность пропустить процесс самостоятельной компиляции.
Понадобятся:
- Linux
- Компиляторы C и C++, autoconf 2.13
- Исходники nginx
- TraceMonkey из репозитория
- Библиотека NSPR
- Наш модуль
- MySQL и SQLite (опционально) + средства разработки
Сначала NSPR последней версии (на момент написания — 4.8.2):
wget ftp://ftp.mozilla.org/pub/mozilla.org/nspr/releases/v4.8.2/src/nspr-4.8.2.tar.gz<br/>tar -xzf nspr-4.8.2.tar.gz<br/>cd nspr-4.8.2/mozilla/nsprpub<br/>./configure --prefix=/usr/local --with-pthreads<br/>make<br/>sudo make install<br/>Затем TraceMonkey из репозитория (на момент написания, в репозитории версия 1.8.5, а скачать файл с исходниками можно только для 1.7.0):
hg clone http://hg.mozilla.org/tracemonkey/<br/>cd tracemonkey/js/src<br/>autoconf2.13<br/>./configure --prefix=/usr/local --with-nspr-prefix=/usr/local --with-system-nspr --with-pthreads --enable-threadsafe<br/>make<br/>sudo make install<br/>Этот шаг может быть проблемным по нескольким причинам. Во-первых, не у всех есть команда
hg. А во-вторых, из репозитория скачиваются все исходники Mozilla Firefox. Поэтому, первую строчку кода можно заменить и скачать исходники только TraceMonkey:# hg clone http://hg.mozilla.org/tracemonkey/<br/>wget http://js.nnov.ru/files/tracemonkey-20100119.tar.gz<br/>tar -xzf tracemonkey-20100119.tar.gz<br/>И затем уже скомпилировать.
Далее nginx (0.8.32) и модуль javascript:
wget http://sysoev.ru/nginx/nginx-0.8.32.tar.gz<br/>tar -xzf nginx-0.8.32.tar.gz<br/>cd nginx-0.8.32/src/http/modules<br/>svn co http://nginx-javascript.googlecode.com/svn/trunk/ javascript<br/>cd ../../..<br/>./configure --prefix=/usr/local/nginx-javascript --add-module=src/http/modules/javascript<br/>make<br/>sudo make install<br/>Если все получилось — то переходим к настройке. Счастливые обладатели бинарной сборки обнаружат, что конфигурация уже выполнена, но лишний раз проверить не помешает. Достаточно выполнить следующие шаги:
- Добавить в mime.types тип application/x-javascript-serverside для файлов, которые будут обрабатываться как javascript:
# /usr/local/nginx-javascript/conf/mime.types<br/>types {<br/> ...<br/> application/x-javascript-serverside jsx;<br/> ...<br/>}<br/>
Расширение .jsx выбрано вместо стандартного .js, чтобы сервер не обрабатывал обычные java-скрипты как серверные - Разрешить в разделе location / файла nginx.conf обработку javascript. Заодно сменим номер порта, на котором будет работать сервер:
# /usr/local/nginx-javascript/conf/nginx.conf<br/>...<br/> server {<br/> listen 8081;<br/> ...<br/> location / {<br/> ...<br/> javascript on;<br/> ...<br/> }<br/> }<br/>...<br/> - Запустить nginx:
/usr/local/nginx-javascript/sbin/nginx<br/> - Создать тестовый скрипт hello.jsx:
// /usr/local/nginx-javascript/html/hello.jsx<br/>print("Hello, people!");<br/> - Проверить, что hello.jsx выглядит в браузере как надо:
curl http://localhost:8081/hello.jsx<br/>
В сравнении участвовали:
- Apache/2.2.14 (prefork) + PHP/5.2.12 (модуль)
- nginx/0.8.32 (1 рабочий процесс) + javascript
- nginx/0.8.32 (8 рабочих процессов) + javascript
Сначала цикл тестов из 1000 запросов один за другим:
# Apache 2.2.14 (prefork) + PHP 5.2.12 (module)<br/>ab -n 1000 http://localhost:8085/hello.php<br/>Time per request: 5.278 [ms] (mean, across all concurrent requests)<br/># nginx (1 worker) + javascript<br/>ab -n 1000 http://localhost:8081/hello.jsx<br/>Time per request: 1.298 [ms] (mean, across all concurrent requests)<br/># nginx (8 workers) + javascript<br/>ab -n 1000 http://localhost:8088/hello.jsx<br/>Time per request: 1.322 [ms] (mean, across all concurrent requests)<br/>Теперь цикл тестов из 1000 запросов при создании 100 одновременных подключений:
# Apache 2.2 (prefork) + PHP 5.2 (module)<br/>ab -n 1000 -c 100 http://localhost:8085/hello.php<br/>Time per request: 1.648 [ms] (mean, across all concurrent requests)<br/># nginx (1 worker) + javascript<br/>ab -n 1000 -c 100 http://localhost:8081/hello.jsx<br/>Time per request: 1.277 [ms] (mean, across all concurrent requests)<br/># nginx (8 workers) + javascript<br/>ab -n 1000 -c 100 http://localhost:8088/hello.jsx<br/>Time per request: 0.544 [ms] (mean, across all concurrent requests)<br/>Выводы из тестирования:
- Если запросы к серверу идут последовательно, один за другим, nginx+javascript работает значительно быстрее (у нас примерно в 3 раза). При этом nginx с одним рабочим процессом работает даже чуть-чуть быстрее. В реальности такая ситуация практически никогда не происходит: чаще много клиентов открывают одновременно разные страницы
- Если запросы к серверу отправляются одновременно, скорость работы apache+php увеличивается (у нас они показали почти такую же скорость, как nginx+javascript с одним рабочим процессом). Но и скорость работы nginx+javascript с несколькими рабочими процессами тоже возрастает (у нас — более, чем в 2 раза). А nginx+javascript с одним рабочим процессом осталась практически неизменной
// Выводит параметры id запросов GET, POST и cookies:<br/>print($request.get['id'], " ", $request.post['id'], " ", $request.cookie['id']);<br/>// Отправляет заголовок Content-Type:<br/>$result.headers.push("Content-Type: text/html; charset=UTF-8");<br/>// Открывает базу данных, выполняет запрос SELECT с параметром, переданным в GET, и забирает одну строку результата:<br/>var row = (new SQLite("database")).query("SELECT * FROM `table` WHERE `id`=?", $request.get['id']).fetch();<br/>// Читает файл:<br/>print(File.open("index.html").getChars());<br/>// Выводит IP-адрес клиента, открывашего страницу:<br/>print({$server.remoteAddr});<br/>В последнем примере нет ошибки синтаксиса, XML-документы действительно можно использовать внутри скриптов. При этом в них можно вставлять обращения к переменным и вызовы функций, заключая их в фигурные скобки. Эту технологию, E4X, очень удобно применять для создания шаблонов. Еще ряд примеров можно найти на http://js.nnov.ru/nginx/examples.html.
Конечно, есть еще ряд проблем, которые нужно постепенно решить:
- Поддержка загрузки файлов (coming soon!)
- Поддержка cURL и GD, без которых очень сложно жить
- Оптимизация системных вызовов stat(), которые сейчас используются для определения определения реального пути к файлу
З.Ы.> Отдельное спасибо FTM за инвайт, благодаря которому топик уже не в песочнице
UPD> Сразу бы опубликовал в тематическом, но были проблемы с кармой. Спасибо всем участвующим!