![](https://habrastorage.org/files/9e1/304/a43/9e1304a43e2a4a93a6105687a5e0626e.png)
Дело было вечером, делать было нечего, но голова рукам покоя не давала и хотелось чего-то для души… А для души захотелось чего-то новенького, эдакого необычного.
Я, как и многие из хабровчан, люблю новинки. Релиз нового софта — это как праздник. Новые фичи, новые возможности… Новые способы забивать гвозди и кататься на велосипедах. Новые велосипеды… В общем можно придумать кучу аллегорий и метафор. А про что это я? Ах да, про Nginx, HTTP2 и JavaScript. Чем они связаны, спросите вы? А тем, что в последней версии Nginx (1.9.5) добавили много интересных плюшек, а именно:
- добавили протокол HTTP2 и удалили модуль SPDY (нафиг старое барахло)
- интегрировали прямо в nginx модуль ngx_http_js_module и создали свой диалект JavaScript
Го под кат, расскажу детали.
Собственно, меня заинтересовали именно эти две фичи. Я опытный воин, перешедший из лагера (опечатка по фрейду – люблю лагер и темный эль) бекендеров на сторону фронтендеров. Но я продолжаю следить за любимыми инструментами, среди которых Ngnix.
Про HTTP2
Говорить много не буду. Он работает. Причем конфиг проще чем для SPDY. Ну или похожий. Чтобы собрать сервер с поддержкой HTTP2, при конфигурации добавляем параметр:
--with-http_v2_module
И все. Все просто, никаких патчей и сторонних либ, просто одна строчка при конфигурации перед сборкой. На хабре можно найти информацию по HTTP2:
Про JavaScript
О, это самое интересное! Фронтендеры, спешу сообщить вам, что у нас появился новый диалект JavaScript (ну нам не привыкать если чо) — это nginScript (энджин скрипт). Ура! Я всегда знал, что если у программиста есть микроскоп, то все вокруг кажется гвоздями. Шутка, дорогие программисты, шутка.
Скрипты на JavaScript могут использоваться прямо в файле конфигурации (!) для определения расширенной логики обработки запросов. Для формирования конфигурации, для динамической генерации ответа. Так же можно делать модификацию запроса и ответа. А еще можно быстро создавать заглушки с решением проблем в web-приложениях (aka «костыли» или временно сделаем так, а потом переделаем как надо). Это просто крутатошка крутатенюшка, вам скажу!
Сам скрипт запускается посредством директивы js_run и позволяет прямо на стороне сервера (что я несу?)… Нет, прямо в самом сервере выполнять многие низкоуровневые операции с запросом, без необходимости написания отдельного модуля на языке Си/Lua или на чем там еще пишут и решают такие задачи.
Для выполнения скриптов используется собственный движок njs. Для этого команда разработчиков Nginx реализовала свою версию виртуальной машины под урезанное подмножество языка JavaScript. Собственно, этот язык и назвали nginScript, дабы не путали с JavaScript, так как все же это именно подмножество. Что интересно: на каждый запрос запускается отдельная виртуальная машина, что позволяет обойтись без сборщика мусора. Язык JavaScript выбран как наиболее популярный язык программирования. Lua был хорошим претендентом, но он не так широко распространен в кругах web-разработчиков. Необходимость создания собственной виртуальной машины JavaScript обусловлена тем, что существующие движки оптимизированы для работы в браузере, в то время как для Nginx необходима не просто серверная реализация, но интегрированная в движок. В общем не потянули разработчики заюзать полноценный V8. Поэтому это JavaScript, но писать надо как под IE6, без выпендрежа. Все же это конфиг, если что.
NginScript обладает виртуальной машиной и компилятором байт кода с быстрым запуском и завершением работы. Блокирующие операции, такие как подзапросы HTTP, могут быть приостановлены и возобновлены по аналогии с другими блокирующими операциями в JS.
В язык описания конфигов Nginx добавлены синтаксические инструкции, позволяющие встраивать блоки кода на JS прямо в файл конфигурации. Подобные блоки выполняются по мере обработки HTTP-транзакций и позволяют для каждого запроса выполнять такие операции как корректировка внутренних параметров nginx, создание сложных условий, изменение запроса или ответа.
С помощью nginScript можно описывать конфиги, которые без написания дополнительных расширений и модулей могут динамически блокировать вредоносные запросы, эксплуатирующие уязвимости в web-приложениях или ограничивать интенсивность определённых запросов. Так же можно реализовывать гибкие правила перенаправления трафика, использующие информацию из запроса и не только.
А еще можно делать горячие костыли! Мы же можем исправлять ошибки в web-приложениях, изменять бизнес-логику, распределять запросы на несколько серверов с последующей агрегацией ответов от них и много чего еще… И все это прямо в конфиге. И все это на знакомом нам языке, который мы любим.
Так, много буков, давайте перейдем к установке и примерам.
Установка
Если не установлен меркуриал – ставим. А далее по инструкции:
# Obtain the latest source for NGINX from http://nginx.org/en/download.html
$ wget http://nginx.org/download/nginx-1.9.5.tar.gz
$ tar -xzvf nginx-1.9.5.tar.gz
# Obtain the development sources for nginScript
$ hg clone http://hg.nginx.org/njs
# Build and install NGINX
$ cd nginx-1.9.5
$ ./configure \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--user=www \
--group=www \
--with-ipv6 \
--with-pcre-jit \
--with-http_gzip_static_module \
--with-http_ssl_module \
--with-http_v2_module \
--add-module=../njs/nginx \
\
$ make -j2 && make install
nginScript aka JavaScript
Мы можем заранее предопределять переменные с результатами вычисления через директиву js_set:
js_set $js_txt "
var m = 'Hello ';
m += 'world!';
m;
";
server {
listen :443 ssl http2;
server_name edu.tutu.ru;
set $sname edu.tutu.ru;
set $root "/www/sites/$sname/www/public";
ssl_certificate /etc/nginx/ssl/edu.tutu.ru.pem;
ssl_certificate_key /etc/nginx/ssl/edu.tutu.ru.key;
include base.conf;
location /helloworld {
add_header Content-Type text/plain;
return 200 $js_txt;
}
}
Как видите, в качестве возвращаемого значения используется необычная инструкция. Странно, почему не сделали слово return. Прямо неожиданно и необычно. Но да ладно, сделали так сделали.
Как я понял директиву можно устанавливать вне блоков server. Иначе парсер ругается.
Вы можете сразу из конфига сгенерить контент и отдать его в браузер:
location /helloworld {
js_run "
var res;
res = $r.response;
res.contentType = 'text/plain';
res.status = 200;
res.sendHeader();
res.send('Hello, world!');
res.finish();
";
}
Давайте сделаем http_dump, м?
js_set $http_dump "
var a, s, h;
s = 'Request summary\n\n';
s += 'Method: ' + $r.method + '\n';
s += 'HTTP version: ' + $r.httpVersion + '\n';
s += 'Host: ' + $r.headers.host + '\n';
s += 'Remote Address: ' + $r.remoteAddress + '\n';
s += 'URI: ' + $r.uri + '\n';
s += 'Headers:\n';
for (h in $r.headers)
s += ' header \"' + h + '\" is \"' + $r.headers[h] + '\"\n';
s += 'Args:\n';
for (a in $r.args)
s += ' arg \"' + a + '\" is \"' + $r.args[a] + '\"\n';
s;
";
server {
listen :443 ssl http2;
server_name edu.tutu.ru;
set $sname edu.tutu.ru;
set $root "/www/sites/$sname/www/public";
ssl_certificate /etc/nginx/ssl/edu.tutu.ru.pem;
ssl_certificate_key /etc/nginx/ssl/edu.tutu.ru.key;
location /js/httpdump {
add_header Content-Type text/plain;
return 200 $js_summary;
}
}
А давайте напишем сервис для расчёта чисел Фибоначчи
Если есть микроскоп, то почему бы им не забить гвоздь? На вопрос зачем, настоящий программист/ученый/изобретатель всегда ответят: потому что я могу это сделать! Да, я могу написать такой сервис, не выходя из конфига Nginx, не устанавливая Nodejs, PHP или Ruby. Я могу все это сделать внутри сервера! Да!
location /js/getfib {
js_run "
function fib(n) {
var prev = 1,
curr = 1,
newc,
i;
if (n < 2) return n;
for (i = 3; i <= n; i++) {
newc = prev + curr;
prev = curr;
curr = newc;
}
return curr;
}
var n = +$r.args['n'],
txt = 'Fibonacci( ' + n + ' ) = ' + fib(n),
res = $r.response;
res.contentType = 'text/plain';
res.status = 200;
res.sendHeader();
res.send(txt);
res.send('\n');
res.finish();
";
}
Я хотел написать вместо:
newc = prev + curr;
prev = curr;
curr = newc;
деструктурированный код:
[ prev, curr ] = [ curr, prev + $curr ];
Но nginScript ничего не знает про ES2015/ES6. Тогда я написал так:
prev = [ curr, prev += curr ][0];
И тут тоже был фейл. Оказывается так писать тоже нельзя. Так что, друзья, не все можно. Эдакий IE5 получается. Но в целом мне понравилось.
Хорошего вам, чего у вас сейчас там.
Ссылки по теме
UPD
Пример привязки апстримов и динамического роутинга
upstream my_upstream0 {
server server1.example.com;
server server2.example.com;
}
upstream my_upstream1 {
server server3.example.com;
server server4.example.com;
}
js_set $my_upstream "
var s, upstream, upstream_num;
upstream = $r.args.upstream;
// convert upstream number to integer
upstream_num = +upstream | 0;
if (upstream_num < 0 || upstream_num > 1) {
upstream_num = 0;
}
s = 'my_upstream' + upstream_num;
s;
";
server {
listen 80;
location / {
proxy_set_header Host $host;
proxy_pass http://$my_upstream;
}
}