JavaScript and Nginx = nginScript, and HTTP2 to boot


    It was in the evening, there was nothing to do, but the head did not give rest to hands and I wanted something for the soul ... And for the soul I wanted something new, kind of unusual.

    I, like many of the Khabrovsk citizens, love new items. The release of new software is like a holiday. New features, new features ... New ways to hammer in nails and ride bicycles. New bicycles ... In general, you can come up with a bunch of allegories and metaphors. And what am I talking about? Oh yes, about Nginx, HTTP2 and JavaScript. What are they related to, you ask? And the fact that in the latest version of Nginx (1.9.5) added a lot of interesting buns, namely:

    • added the HTTP2 protocol and removed the SPDY module (see the old junk)
    • integrated the ngx_http_js_module module directly into nginx and created their own JavaScript dialect

    Go under the cut, I’ll tell you the details.

    Actually, these two features interested me. I am an experienced warrior who has moved from a lager (typo according to Freud - I love lager and dark ale) of beckenders to the front-end side. But I continue to follow my favorite tools, including Ngnix.

    About HTTP2


    I won’t talk much. He works. And the config is easier than for SPDY. Well, or similar. To build a server with HTTP2 support, add the parameter during configuration:

    	--with-http_v2_module
    

    And that’s all. Everything is simple, no patches and third-party libs, just one line when configuring before assembly. On the hub you can find information on HTTP2:

    1. Http2 explanation
    2. Test version of HTTP / 2 module for NGINX published

    About JavaScript


    Oh, this is the most interesting! Frontenders, I hasten to inform you that we have a new dialect of JavaScript (well, we don’t get used to it if cho) - this is nginScript (engin script). Hurrah! I always knew that if a programmer has a microscope, then everything seems like nails. A joke, dear programmers, a joke.

    JavaScript scripts can be used directly in the configuration file (!) To define extended request processing logic. To form a configuration, to dynamically generate a response. You can also modify the request and response. And you can quickly create stubs with problem solving in web applications (aka “crutches” or temporarily do it, and then redo it as it should). This is just a little twist, I'll tell you!

    The script itself is launched using the js_run directive and allows directly on the server side (what am I carrying?) ... No, right in the server itself to perform many low-level operations with the request, without the need to write a separate module in C / Lua or what else they write and solve there such tasks.

    To execute the scripts, the native njs engine is used. For this, the Nginx development team implemented its version of the virtual machine under a truncated subset of the JavaScript language. Actually, this language was called nginScript, so as not to be confused with JavaScript, since nevertheless this is a subset. What is interesting: a separate virtual machine is launched for each request, which eliminates the need for a garbage collector. JavaScript is selected as the most popular programming language. Lua was a good contender, but it is not so widespread in the circles of web-developers. The need to create your own JavaScript virtual machine is due to the fact that existing engines are optimized for working in a browser, while Nginx requires not just a server implementation, but also an integrated one in the engine. In general, the developers did not pull the full V8. Therefore, this is JavaScript, but you need to write like under IE6, without showing off. Nevertheless, this is a config, if that.

    NginScript has a virtual machine and a byte code compiler with quick start and shutdown. Blocking operations, such as HTTP subqueries, can be paused and resumed, similar to other blocking operations in JS.

    Syntax instructions have been added to the Nginx config description language, allowing you to embed JS code blocks directly in the configuration file. Such blocks are executed as HTTP transactions are processed and allow for each request to perform operations such as adjusting nginx internal parameters, creating complex conditions, changing the request or response.

    Using nginScript, you can describe configs that, without writing additional extensions and modules, can dynamically block malicious requests that exploit vulnerabilities in web applications or limit the intensity of certain requests. It is also possible to implement flexible traffic redirection rules that use information from the request and not only.

    And you can make hot crutches! We can correct errors in web applications, change business logic, distribute requests to several servers, followed by aggregation of responses from them and much more ... And all this is right in the config. And all this is in the familiar language that we love.

    So, a lot of beeches, let's move on to installation and examples.

    Installation


    If mercury is not installed - set. And then follow the instructions:

    # 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


    We can predefine variables with calculation results through the js_set directive :

    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;
         }
    }
    

    As you can see, an unusual instruction is used as the return value. It is strange why they did not make the word return. Directly unexpected and unusual. But come on, they did it.

    As I understand it, the directive can be installed outside of server blocks. Otherwise, the parser swears.

    You can immediately generate content from the config and send it to the browser:

    location /helloworld {
        js_run "
            var res;
            res = $r.response;
            res.contentType = 'text/plain';
            res.status = 200;
            res.sendHeader();
            res.send('Hello, world!');
            res.finish();
        ";
    }
    

    Let's do http_dump, m?

    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;
        }
    }
    

    And let's write a service for calculating Fibonacci numbers


    If there is a microscope, then why not hammer a nail? When asked why, a real programmer / scientist / inventor will always answer: because I can do it! Yes, I can write such a service without leaving the Nginx config, without installing Nodejs, PHP or Ruby. I can do all this inside the server! Yes!

    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();
        ";
    }
    

    I wanted to write instead:

    newc = prev + curr;
    prev = curr;
    curr = newc;
    

    broken code:

    [ prev, curr ] = [ curr, prev + $curr ];
    


    But nginScript doesn't know anything about ES2015 / ES6. Then I wrote like this:

    prev = [ curr, prev += curr ][0];
    

    And there was a feil too. It turns out that writing is also impossible. So, friends, not everything is possible. A kind of IE5 is obtained. But overall I liked it.
    Good to you what you have there right now.

    Related Links




    UPD

    An example of binding upstream and dynamic routing


    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;
        }
    }
    

    Also popular now: