nginScript 同时支持 HTTP 和 TCP/UDP 两种协议,所以它的应用场景很广。
除此之外,nginScript 还有很多其他特性,不过还有很多特性还没有实现。虽然我们发布了具有一般可用性的 nginScript 版本,并且可被用于生产环境,不过,我们仍然计划了一个演化线路图,包含了更多的应用场景。
在更详细地讨论 nginScript 之前,我们先来澄清两个概念。
多年来,NGINX 社区创建了很多编程式扩展,Lua 是其中最受欢迎的一个。Lua 被作为 NGINX 的一个模块,也是通过 NGINX Plus 认证的第三方模块。Lua 模块和它的扩展插件与 NGINX 内核深度集成,提供了非常丰富的功能,其中就包括 Redis 的驱动程序。
Lua 是一个强大的脚本语言,不过,在市场采用率方面仍然不是很理想,而且对于一线开发人员和 DevOps 工程师来说,它也不在他们的 "必备技能" 之列。
nginScript 并不是要取代 Lua,nginScript 的目标是要通过使用一门流行的编程语言为广大的社区提供一种编程式的配置方案。
nginScript 的目标并不在于要把 NGINX 或者 NGINX Plus 升格成为应用服务器。简单地说,nginScript 的应用场景有点类似于中间件,因为 nginScript 的代码运行在客户端和服务器内容之间。从技术角度来看,nginScript 和 NGINX(或 NGINX Plus)组合在一起之后有点类似 Node.js,不过它们与 Node.js 的相似之处也仅限于两点,即采用了基于事件驱动的架构以及使用 JavaScript 作为编程语言。
Node.js 使用的是 Google 的 V8 JavaScript 引擎,而 nginScript 实现了 ECMAScript 标准,是为了 NGINX 和 NGINX Plus 而特别设计的。Node.js 在内存里运行稳定的 JavaScript 虚拟机,并通过垃圾回收来管理内存,而 nginScript 会为每个请求启动一个 JavaScript 虚拟机,并为其分配必要的内存,在请求处理完毕之后清理内存。
上面已经提到过,nginScript 是 JavaScript 的一个定制实现。其他大部分 JavaScript 运行时引擎都是为浏览器而设计的。客户端的代码执行属性在很多方面与服务器端的不一样,这些不同点表现在系统资源的可用性和可创建的并发运行时数量等方面。
我们之所以要实现自己的 JavaScript 运行时,是为了满足服务器端代码的执行需求,并与 NGINX 的请求处理架构保持兼容。nginScript 的设计遵循了如下原则。
下面的表格列出了可以通过 nginScript 来访问的处理阶段,以及相应的配置指令。
处理阶段 |
HTTP 模块 |
流(TCP/UDP)模块 |
访问阶段——网络连接访问控制 |
X |
js_access |
预读取阶段——读取 / 写入 body |
X |
js_preread |
过滤阶段——在代理期间读取 / 写入 body |
X |
js_filter |
返回内容阶段——发送响应给客户端 |
js_content |
X |
日志 / 变量——按需计算 |
js_set |
js_set |
我们可以将 nginScript 作为模块编译到 NGINX 里,也可以动态地将其加载到 NGINX 或 NGINX Plus 里。这篇文章的末尾将介绍如何在 NGINX 和 NGINX Plus 里启用 nginScript。
下面的例子使用 NGINX 或 NGINX Plus 作为简单的反向代理,并用 nginScript 构造具有特定格式的访问日志,每个日志里包含了如下几项内容。
NGINX 的配置非常简单:
- js_include conf.d/header_logging.js; # Load JavaScript code from here
- js_set $access_log_with_headers kvAccessLog; # Fill variable from JS function
- log_format kvpairs $access_log_with_headers; # Define special log format
- server {
- listen 80;
- access_log /var/log/nginx/access_headers.log kvpairs;
- location / {
- proxy_pass http://www.example.com;
- }
- }
从上面可以看出,配置里并没有直接包含 nginScript 代码。我们使用 js_include 指令指定包含了 JavaScript 代码的文件。js_set 指令定义了一个 NGINX 变量 $access_log_with_headers,以及处理这个变量的 JavaScript 函数。log_format 指令定义了新的格式 kvpairs,日志里的每一行将包含 $access_log_with_headers 的值。server 代码块定义了一个简单的 HTTP 反向代理,它将所有的请求转向到 http://www.example.com。access_log 指令表示所有的请求消息将会以 kvpairs 的格式被记录下来。
现在来看一下格式化日志的 JavaScript 代码。我们有两个函数:
- function kvHeaders(headers, parent) {
- var kvpairs = "";
- for (var h in headers) {
- kvpairs += " " + parent + "." + h + "=";
- if ( headers[h].indexOf(" ") == -1 ) {
- kvpairs += headers[h];
- } else {
- kvpairs += "'" + headers[h] + "'";
- }
- }
- return kvpairs;
- }
- function kvAccessLog(req, res) {
- var log = req.variables.time_iso8601; // nginScript可以访问所有变量
- log += " client=" + req.remoteAddress; // request对象的属性
- log += " method=" + req.method; // "
- log += " uri=" + req.uri; // "
- log += " status=" + res.status; // response对象的属性
- log += kvHeaders(req.headers, "req"); // 把request header对象传给函数
- log += kvHeaders(res.headers, "res"); // 把response header对象传给函数
- return log;
- }
kvAccessLog 函数的返回值被传给了 js_set 配置指令。NGINX 变量是按需进行计算的,也就是说,只有当变量需要被用到的时候,js_set 定义的函数才会被执行。在这个例子里,log_format 指令使用了变量 $access_log_with_headers,所以 kvAccessLog() 函数会在记录日志时执行。
map 和 rewrite 指令所使用的变量会在早期处理阶段触发执行相应的 JavaScript 代码。
我们可以向反向代理发送一个请求,并观察日志文件,以便验证这个方案。
- $ curl http: //127.0.0.1/
- $ tail--lines = 1 /
- var / log / nginx / access_headers.log 2017 - 03 - 14T14: 36 : 53 + 00 : 00 client = 127.0.0.1 method = GET uri = /
- status=200 req.Host=127.0.0.1 req.User-Agent=curl/7.47.0 req.Accept = *
- /* res.Cache-Control=max-age=604800 res.Etag=\x22359670651+
- ident\x22 res.Expires='
- Tue, 21 Mar 2017 14:36:53 GMT'
- res.Last-Modified='Fri, 09 Aug 2013 23:54:35 GMT'
- res.Vary=Accept-Encoding res.X-Cache=HIT*/
大多数情况下,我们使用 nginScript 来访问 NGINX 的内部结构。上面的例子还利用了 request 和 response 对象的一些属性,TCP/UDP 的 Stream nginScript 模块则利用了 session 对象的一些属性。
来源: http://www.infoq.com/cn/articles/introduction-to-nginscript