JavaScript и Nginx = nginScript, а HTTP2 в придачу

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:

  1. Разъяснение http2
  2. Опубликована тестовая версия модуля HTTP/2 для NGINX

Про 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-приложениях или ограничивать интенсивность определённых запросов. Так же можно реализовывать гибкие правила перенаправления трафика, использующие информацию из запроса и не только.

Теоретически, nginScript дает платформу для быстрой разработки и реиспользования приложений за счет использования типовых библиотек функций. Проще говоря, код на nginScript может быть вынесен в библиотеки и использован другими разработчиками.

А еще можно делать горячие костыли! Мы же можем исправлять ошибки в 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 получается.
Но в целом мне понравилось. Хорошего вам, чего у вас сейчас там.

Ссылки по теме


© Habrahabr.ru