编者按 —— 题为“NGINX JavaScript 模块简介”的博文已重定向到此处。本文已更新,所涉及的 NGINX JavaScript 模块指令和特性截至 2021 年 4 月均受支持。
NGINX JavaScript 模块 (njs) 现已作为 NGINX 开源版 1.11.10 和 NGINX Plus R12 中的稳定模块正式推出。[该模块最初称为 nginxScript,一些早期博文中均使用这一名称。] 自 2015 年 9 月 NGINX JavaScript 推出以来,我们一直在稳步推进各项工作,为这一稳定模块添加了不同特性和语言支持。
NGINX JavaScript 是面向 NGINX 和 NGINX Plus 的一种独特的 JavaScript 实现,专为服务器端用例和按请求处理的情况而设计。通过使用 JavaScript 代码扩展了 NGINX 的配置语法,从而使得 NGINX 能够实现复杂配置。
NGINX JavaScript 模块的用例非常广泛,特别是可同时适用于 HTTP 和 TCP/UDP 协议。NGINX JavaScript 的示例用例包括:
- 可使用常规 NGINX 中未定义的变量生成自定义的日志格式(例如:诊断日志)
- 修改来自代理服务器的响应(响应过滤)
- 实施自定义身份验证方案(例如:OAuth 2.0 令牌自省 token introspection)
- 解析应用级粘性会话的 TCP/UDP 协议(例如 MQTT 负载均衡)
在更详细地探讨 NGINX JavaScript 之前,我们首先来消除两个常见误解。
NGINX JavaScript 不是 Lua
这些年来,NGINX 社区创建了多个编程扩展模块。在撰写本文时,Lua 是其中最受欢迎的语言模块。它可作为 NGINX 的模块使用,也可以充当 NGINX Plus 支持的预构建第三方动态模块。Lua 模块及其插件库与 NGINX 的内核进行了深度集成,并提供了一套丰富的功能,其中包括 Redis 的驱动程序。
Lua 是一种强大的脚本语言,然而其并未得到广泛采用,而且通常不会出现在前端开发人员或 DevOps 工程师的“技能工具箱”中。
NGINX JavaScript 并非意在取代 Lua——它仍需时日才会具备与之相当的功能。NGINX JavaScript 的目标是使用一种常见编程语言为最大范围的社区提供编程配置解决方案。
NGINX JavaScript 不是 Node.js
简单地说,由于 JavaScript 代码在客户端和内容之间执行,NGINX JavaScript 的用例比较类似于中间件。从技术角度来说,对于 NGINX JavaScript 与 NGINX 或 NGINX Plus 的组合,尽管 Node.js 与其有两点相似之处 —— 事件驱动型架构和 JavaScript 编程语言 —— 但也仅此而已。
Node.js 使用 Google V8 JavaScript 引擎,而 NGINX JavaScript 则是基于 ECMAScript 标准的定制化实现,专为 NGINX 和 NGINX Plus 而设计。Node.js 在内存中有一个持久化的 JavaScript 虚拟机 (VM),执行日常垃圾回收以管理内存;而 NGINX JavaScript 针对每个请求都会初始化一个新的 JavaScript VM 以及其所需的内存,并在请求完成时释放内存空间。
JavaScript 即服务器端语言
如上所述,NGINX JavaScript 是 JavaScript 语言的定制实现。其他所有现有的 JavaScript 运行时引擎都是为在 Web 浏览器中执行代码而设计的——客户端代码执行的要求和情况在许多方面都和服务器端代码执行有所不同,包括系统资源的可用性和可能的并发运行时数量。
我们决定实现自己的 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 JavaScript 作为模块实现,支持您将其编译成 NGINX 开源版二进制文件,或者动态加载到 NGINX 或 NGINX Plus 中。文末提供了在 NGINX 和 NGINX Plus 上启用 NGINX JavaScript 的操作说明。
在此示例中,我们使用 NGINX 或 NGINX Plus 作为一个简单的反向代理,并采用 NGINX JavaScript 构建特定格式的访问日志的输入条目,这些输入条目:
- 包括客户端发送的请求标头
- 包括后端返回的响应标头
- 针对 ELK 堆栈(现称为“弹性堆栈”)、Graylog 及 Splunk 等日志处理工具,使用键值对以高效地进行注入和搜索。
本例中的 NGINX 配置非常简单。
如您所见,NGINX JavaScript 代码并不内嵌在配置语法内。相反,我们使用 js_import
指令来导入包含了所有 JavaScript 代码的文件。js_set
指令定义了一个新的 NGINX 变量 $access_log_headers
,以及填充它的 JavaScript 函数。log_format
指令定义了一种名为 kvpairs 的新格式,它能够将 $access_log_headers
的值写入每个日志行。
server
块定义了一个简单的 HTTP 反向代理,可将所有请求转发到 https://www.example.com。access_log
指令指定了所有请求均将采用 kvpairs 格式进行记录。
现在,我们来看看准备日志条目的 JavaScript 代码。
kvAccess
函数的返回值(一个日志条目)被传递到 rawheader_logging.conf 中的 js_set
配置指令。请谨记,NGINX 变量只有在被需要的时候才会进行求值计算,这意味着 js_set
定义的 JavaScript 函数只在需要该变量的值时才执行。在此示例中,由于 $access_log_headers
被用于 log_format
指令,因此 kvAccess()
在日志记录时间执行。而用作 map
或 rewrite
指令的一部分的变量(本例未说明)会在早期处理阶段触发相应的 JavaScript 执行。
以下是 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 的大部分实用程序均通过访问 NGINX 内部代码实现。本示例就使用了请求 (r
) 对象的多个属性。Stream NGINX JavaScript 模块(面向 TCP 和 UDP 应用)使用了一个带有其属性集的会话对象 (s
) 。如欲了解面向 HTTP 和 TCP/UDP 的 NGINX JavaScript 解决方案的其他示例,请参阅“NGINX JavaScript 模块的用例”。
NGINX JavaScript 模块的用例
请查看以下博文,了解 NGINX JavaScript 模块的其他 HTTP 和 TCP/UDP 用例:
- “使用 TCP 负载均衡和 Galera 集群扩展 MySQL”中的“借助 NGINX JavaScript 模块实现高级日志记录”
- “隆重推出 NGINX Plus R24” —— 正文和标头的响应过滤;使用嵌入式 HTTP 客户端验证 TCP/UDP 连接
- “将 NGINX Plus 部署为 API 网关,第 2 部分:保护后端服务”中的“验证请求正文”
- “使用 NGINX JavaScript 模块进行诊断日志记录”
- “隆重推出 NGINX Plus R22” —— 遇到错误时,记录客户端发送的具体标头集
- “隆重推出 NGINX Plus R21” —— 在单个代码序列中链接子请求而不使用回调;将请求标头的副本发送至安全信息和事件管理 (SIEM) 系统
- “只需一个 POST:通过 F5 解决方案和 NGINX JavaScript 模块启用声明式 DNS”
- “使用 NGINX 和 NGINX Plus 验证 OAuth 2.0 访问令牌”中的“借助 NGINX JavaScript 模块扩展
auth_request
” 。 - “利用 NGINX 支持物联网设备空中更新”
- “隆重推出 NGINX Plus R18” —— 记录哈希(屏蔽)版客户端 IP 地址,而非真实 IP 地址
- “通过 NGINX JavaScript 模块执行虚拟修复”
- “NGINX Plus 和 NGINX JavaScript 模块助力批处理 API 请求”
- “隆重推出 NGINX Plus R15” —— 同时向两个不同的后端发出 HTTP 请求,然后转发第一个响应,忽略第二个响应;将数据完整性添加至应用 cookie
- “使用主动运行状况检查检测主页篡改”中的“借助 JavaScript 哈希横向扩展变更检测”
- “借助 NGINX 实现 TCP/UDP 负载均衡:概述、提示和技巧”中的“利用 nginScript 扩展 TCP/UDP 负载均衡”
- “面向物联网的 NGINX Plus:加密和认证 MQTT 流量”中的“使用客户端证书验证 MQTT 客户端”
- “面向物联网的 NGINX Plus:MQTT 负载均衡”中的“使用 NGINX JavaScript 模块实现 MQTT 负载均衡,确保会话持久性”
- “通过 NGINX JavaScript 模块屏蔽数据,保护用户隐私”
- “隆重推出 NGINX Plus R11” —— 在 MySQL 协议流中搜索消息中的关键模式,以识别 SQL 操作
- “使用 NGINX JavaScript 模块将客户端逐步过渡到新服务器”
为 NGINX 和 NGINX Plus 启用 NGINX JavaScript
- 为 NGINX Plus 加载 NGINX JavaScript 模块
- 为 NGINX 开源版加载 NGINX JavaScript 模块
- 将 NGINX JavaScript 编译为 NGINX 开源版的动态模块
为 NGINX Plus 加载 NGINX JavaScript 模块
NGINX JavaScript 可作为免费动态模块供 NGINX Plus 用户使用。有关加载说明,请参阅“NGINX Plus 管理指南”。
为 NGINX 开源版加载 NGINX JavaScript 模块
NGINX JavaScript 模块默认包含在 NGINX Docker 官方镜像中。如果您的系统配置为使用 NGINX 开源版的官方预构建包,并且您安装的版本是 1.9.11 或更高版本,那么您可以安装 NGINX JavaScript 作为您平台的预构建包。
-
安装预构建包。
-
Ubuntu 和 Debian 系统:
$ sudo apt-get install nginx-module-njs
-
RedHat、CentOS 和 Oracle Linux 系统:
$ sudo yum install nginx-module-njs
-
-
在 nginx.conf 配置文件的顶层(“main”)上下文(而非
http
或stream
上下文)中添加一个load_module
指令,以启用该模块。本例面向 HTTP 和 TCP/UDP 流量加载 JavaScript 模块。load_module modules/ngx_http_js_module.so; load_module modules/ngx_stream_js_module.so;
-
重新加载 NGINX Plus,以便将 NGINX JavaScript 模块加载到运行实例中。
$ sudo nginx -s reload
将 NGINX JavaScript 编译为 NGINX 开源版的动态模块
如果您更喜欢从源码编译 NGINX 模块:
- 按照说明从开源仓库构建 HTTP 和/或 TCP/UDP NGINX JavaScript 模块。
- 将模块二进制文件(ngx_http_js_module.so、ngx_stream_js_module.so)复制到 NGINX 根目录的 modules 子目录(通常为 /etc/nginx/modules)。
- 执行“为 NGINX 开源版加载 NGINX JavaScript 模块”的第 2 步和第 3 步。