nginScript 初窥

几年前就有消息称,Nginx 官方要实现一个 JavaScript 实现,作为钦点的内置脚本语言。后来这个实现就被称作 nginScript,简称 njs

这里有来自 Nginx 的一些介绍:https://nginx.org/en/docs/njs_about.html

根据介绍,njs 将会以 ECMAScript 5 作为标准,并且增加部分 ECMAScript 6 的功能,以及一部分自己的扩展。

njs 项目本身,实际上是一个完全独立的 JavaScript 执行环境,可以单独拿出来使用。同时也会提供两个 Nginx 的扩展模块,用来与 Nginx 集成:

这两个模块顾名思义,分别在 Nginx 处理 HTTP 和 stream(TCP/UDP) 请求时使用。

之前 Nginx 还发布了一篇博客来介绍 njs,并阐述了 njs 的定位。从博客来看,njs 的目标与现在在社区中很流行的 lua 模块,以及 OpenResty 定位上重合度还是蛮高的。

njs 与目前的 lua 模块不同的是,lua 模块使用的是原有的 lua 实现,集成在 Nginx 中,但不论是标准的 lua 实现,还是性能更好的 luajit,都不是针对 Nginx 环境进行设计的。

而 njs 是从一开始,就针对 Nginx 环境进行了设计,理论上讲可以有更多性能优化的空间。比如 njs 的实例,被设计成了非常轻量化的实现,不论内存占用,还是启动时间,都比 lua 有更大的优势。因此在前面提到的 ngx_http_js_module 中,njs 可以做到每处理一个请求,就创建一个对应的实例来执行代码,而不像 lua 模块那样,整个进程复用一个 lua 实例。

这样带来了一个好处,njs 的内存申请可以像 Nginx 本身的实现一样,每次处理一个请求的时候,就申请一块内存,来作为这次请求专门的内存空间,不必多次小块的调用 malloc。并且在一次请求处理结束之后,整块的回收掉这块内存,这样 GC 的开销就可以避免了。而 lua 作为一个通用的编程语言实现,GC 的实现也是需要针对各种不同场景的,因此没有办法做到这种专门的优化。

安装

因为 njs 没有正式发布,目前最好的安装方式是直接通过源码编译。编译的方式和大部分 Nginx 模块一样,只要在编译 Nginx 时,在 configure 时指定一下就可以:

$ ./configure --add-module=path-to-njs/nginx/

使用

尝试了一下 ngx_http_js_module,十分简单。这里是 nginx.conf

daemon off;  
master_process off;  
error_log stderr debug;

events {  
    worker_connections  1024;
}

http {  
    js_include hello.js;

    server {
        listen       8080;

        location / {
            js_content hello;
        }
    }
}

配置里开启了一些方便 debug 的功能,比如关闭后台运行,并且直接在主进程中处理请求,以及将日志直接打印在了 stderr 上。

配置文件中的 js_include 指令是引入一个 JS 文件,hello.js 的内容是:

function hello(req, res) {  
  var s = 'Hello nginxScript!';
  req.log('hello!');
  res.status = 200;
  res.sendHeader();
  res.contentLength = s.length;
  res.send(s);
  res.finish();
}

然后在具体的某个 location 里,可以使用 js_content,来指定一个 JS 函数来处理请求。不得不说,目前的 API 设计,和 Node.JS 的 http 模块很像。

除了这种方法之外,还可以通过 js_set 这个指令,将一个 JS 函数指定给某个 Nginx 变量。这样可以在 location 中像正常的 Nginx 变量一样使用这个变量,实际处理强求的时候,会执行这个 JS 函数,并将函数返回值绑定到变量上。