


原文作者:Liam Crilly of F5
原文链接:借助 NGINX JavaScript 模块,充分利用 JavaScript 的强大功能和便利性以快速处理每个请求 - NGINX
转载来源:NGINX 官方网站
NGINX JavaScript 模块 (njs) 现已作为 NGINX 开源版 1.11.10 和 NGINX Plus R12 中的稳定模块正式推出。[该模块最初称为 nginxScript,一些早期博文中均使用这一名称。] 自 2015 年 9 月 NGINX JavaScript 推出以来,我们一直在稳步推进各项工作,为这一稳定模块添加了不同特性和语言支持。
NGINX JavaScript 模块的用例非常广泛,特别是可同时适用于 HTTP 和 TCP/UDP 协议。NGINX JavaScript 的示例用例包括:
- 可使用常规 NGINX 中未定义的变量生成自定义的日志格式(例如:诊断日志)
- 修改来自代理服务器的响应(响应过滤)
- 实施自定义身份验证方案(例如:OAuth 2.0 令牌自省 token introspection)
- 解析应用级粘性会话的 TCP/UDP 协议(例如 MQTT 负载均衡)
NGINX JavaScript 不是 Lua
Lua 是一种强大的脚本语言,然而其并未得到广泛采用,而且通常不会出现在前端开发人员或 DevOps 工程师的“技能工具箱”中。
NGINX JavaScript 不是 Node.js
Node.js 使用 Google V8 JavaScript 引擎,而 NGINX JavaScript 则是基于 ECMAScript 标准的定制化实现,专为 NGINX 和 NGINX Plus 而设计。Node.js 在内存中有一个持久化的 JavaScript 虚拟机 (VM),执行日常垃圾回收以管理内存;而 NGINX JavaScript 针对每个请求都会初始化一个新的 JavaScript VM 以及其所需的内存,并在请求完成时释放内存空间。
JavaScript 即服务器端语言
我们决定实现自己的 JavaScript 运行时,以满足服务器端代码执行的要求,并与 NGINX 的请求处理架构完美契合。我们的 NGINX JavaScript 的设计原则如下:
- 运行时环境与请求的生命周期保持一致
NGINX JavaScript 模块使用单线程字节码执行,专为快速初始化和快速废弃而设计。针对每个请求,运行时环境都会执行一次初始化。因为无需初始化复杂的状态和辅助器,所以启动非常迅速。在执行期间,内存积蓄在池中;执行完成后,则会释放池空间。这一内存管理方案无需跟踪和释放单个对象,也无需使用垃圾回收器。 - 非阻塞代码执行
NGINX 和 NGINX Plus 的事件驱动型模型可调度单个 NGINX JavaScript 运行时环境的执行。当 NGINX JavaScript 规则执行阻塞操作(例如读取网络数据或发出外部子请求)时,NGINX 和 NGINX Plus 将透明地暂停相关 NGINX JavaScript VM 的执行,在事件完成后再重新调度。这意味着您能够以简单的线性方式编写规则,NGINX 和 NGINX Plus 可在不造成内部阻塞的情况下对其进行调度。 - 仅实施我们所需的语言支持
JavaScript 的规范由 ECMAScript 标准定义。NGINX JavaScript 遵循 ECMAScript 5.1 和一些 ECMAScript 6 标准(面向数学函数)。通过实现自己的 JavaScript 运行时,我们能够优先确保对服务器端用例的语言支持,忽略不需要的项目。我们维护着一份当前支持的语言元素列表可供参考。 - 与请求处理阶段紧密集成
NGINX 和 NGINX Plus 在不同的阶段处理请求。配置指令一般在特定阶段运行,因此原生的 NGINX 模块通常支持在特定的阶段检查或修改请求。在 JavaScript 代码执行时,NGINX JavaScript 会通过配置指令暴露一些处理阶段以提供控制权。这种与配置语法的集成能够同时实现原生 NGINX 模块的强大功能和灵活性以及 JavaScript 代码的简单性。
下表显示了在撰写本文时可通过 NGINX JavaScript 访问的处理阶段,以及暴露该阶段的配置指令。处理阶段 HTTP 模块 Stream 模块 访问 —— 身份验证和访问控制 auth_request
和js_content
js_access
预读 —— 读/写有效载荷 不适用 js_preread
过滤 —— 代理期间读/写响应 js_body_filter
js_header_filter
js_filter
内容 —— 向客户端发送响应 js_content
不适用 日志/变量 —— 按需评估 js_set
js_set
NGINX JavaScript 入门 — 真实案例
在此示例中,我们使用 NGINX 或 NGINX Plus 作为一个简单的反向代理,并采用 NGINX JavaScript 构建特定格式的访问日志的输入条目,这些输入条目:
- 包括客户端发送的请求标头
- 包括后端返回的响应标头
- 针对 ELK 堆栈(现称为“弹性堆栈”)、Graylog 及 Splunk 等日志处理工具,使用键值对以高效地进行注入和搜索。
js_import conf.d/logging.js; # Load JavaScript code from here
js_set $access_log_headers logging.kvAccess; # Fill variable from JS function
log_format kvpairs $access_log_headers; # Define special log format
server {
listen 80;
root /usr/share/nginx/html;
access_log /var/log/nginx/access.log kvpairs;
}
如您所见,NGINX JavaScript 代码并不内嵌在配置语法内。相反,我们使用 js_import 指令来导入包含了所有 JavaScript 代码的文件。js_set 指令定义了一个新的 NGINX 变量 $access_log_headers,以及填充它的 JavaScript 函数。log_format 指令定义了一种名为 kvpairs 的新格式,它能够将 $access_log_headers 的值写入每个日志行。
现在,我们来看看准备日志条目的 JavaScript 代码。
function kvAccess(r) {
var log = `${r.variables.time_iso8601} client=${r.remoteAddress} method=${r.method} uri=${r.uri} status=${r.status}`;
r.rawHeadersIn.forEach(h => log += ` in.${h[0]}=${h[1]}`);
r.rawHeadersOut.forEach(h => log += ` out.${h[0]}=${h[1]}`);
return log;
}
export default { kvAccess }
以下是 NGINX JavaScript 增强型日志记录解决方案实例,我们可通过反向代理传递请求,并观察生成的日志文件条目,其中包括带有 in. 前缀的请求标头及带有 out. 前缀的响应标头。
$ curl http://127.0.0.1/
$ tail --lines=1 /var/log/nginx/access_headers.log
2021-04-23T10:08:15+00:00 client=172.17.0.1 method=GET uri=/index.html status=200 in.Host=localhost:55081 in.User-Agent=curl/7.64.1 in.Accept=*/* out.Content-Type=text/html out.Content-Length=612 out.ETag=\x22606339ef-264\x22 out.Accept-Ranges=bytes
NGINX JavaScript 模块的用例
- “使用 TCP 负载均衡和 Galera 集群扩展 MySQL”中的“借助 NGINX JavaScript 模块实现高级日志记录”
- “隆重推出 NGINX Plus R24” —— 正文和标头的响应过滤;使用嵌入式 HTTP 客户端验证 TCP/UDP 连接
- “将 NGINX Plus 部署为 API 网关,第 2 部分:保护后端服务”中的“验证请求正文”
- “隆重推出 NGINX Plus R22” —— 遇到错误时,记录客户端发送的具体标头集
- “隆重推出 NGINX Plus R21” —— 在单个代码序列中链接子请求而不使用回调;将请求标头的副本发送至安全信息和事件管理 (SIEM) 系统
- “使用 NGINX 和 NGINX Plus 验证 OAuth 2.0 访问令牌”中的“借助 NGINX JavaScript 模块扩展 auth_request” 。
- “隆重推出 NGINX Plus R18” —— 记录哈希(屏蔽)版客户端 IP 地址,而非真实 IP 地址
- “隆重推出 NGINX Plus R15” —— 同时向两个不同的后端发出 HTTP 请求,然后转发第一个响应,忽略第二个响应;将数据完整性添加至应用 cookie
- “使用主动运行状况检查检测主页篡改”中的“借助 JavaScript 哈希横向扩展变更检测”
- “借助 NGINX 实现 TCP/UDP 负载均衡:概述、提示和技巧”中的“利用 nginScript 扩展 TCP/UDP 负载均衡”
- “面向物联网的 NGINX Plus:加密和认证 MQTT 流量”中的“使用客户端证书验证 MQTT 客户端”
- “面向物联网的 NGINX Plus:MQTT 负载均衡”中的“使用 NGINX JavaScript 模块实现 MQTT 负载均衡,确保会话持久性”
- “隆重推出 NGINX Plus R11” —— 在 MySQL 协议流中搜索消息中的关键模式,以识别 SQL 操作
为 NGINX 和 NGINX Plus 启用 NGINX JavaScript
为 NGINX 开源版加载 NGINX JavaScript 模块
- 安装预构建包。
Ubuntu 和 Debian 系统:$ sudo apt-get install nginx-module-njs
RedHat、CentOS 和 Oracle Linux 系统
$ sudo yum install nginx-module-njs
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;
$ sudo nginx -s reload
将 NGINX JavaScript 编译为 NGINX 开源版的动态模块
- 将模块二进制文件(ngx_http_js_module.so、ngx_stream_js_module.so)复制到 NGINX 根目录的 modules 子目录(通常为 /etc/nginx/modules)。
- 执行“为 NGINX 开源版加载 NGINX JavaScript 模块”的第 2 步和第 3 步。
更多资源
请前往NGINX开源社区:
- 官网:nginx.org.cn
