
Дело было вечером, делать было нечего, но голова рукам покоя не давала и хотелось чего-то для души… А для души захотелось чего-то новенького, эдакого необычного.
Я, как и многие из хабровчан, люблю новинки. Релиз нового софта — это как праздник. Новые фичи, новые возможности… Новые способы забивать гвозди и кататься на велосипедах. Новые велосипеды… В общем можно придумать кучу аллегорий и метафор. А про что это я? Ах да, про 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; } }
