NGINX.COM
Web Server Load Balancing with NGINX Plus

编者按 —— 题为“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 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_requestjs_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.comaccess_log 指令指定了所有请求均将采用 kvpairs 格式进行记录。

现在,我们来看看准备日志条目的 JavaScript 代码。

kvAccess 函数的返回值(一个日志条目)被传递到 rawheader_logging.conf 中的 js_set 配置指令。请谨记,NGINX 变量只有在被需要的时候才会进行求值计算,这意味着 js_set 定义的 JavaScript 函数只在需要该变量的值时才执行。在此示例中,由于 $access_log_headers 被用于 log_format 指令,因此 kvAccess() 在日志记录时间执行。而用作 maprewrite 指令的一部分的变量(本例未说明)会在早期处理阶段触发相应的 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 用例:


为 NGINX 和 NGINX Plus 启用 NGINX JavaScript

为 NGINX Plus 加载 NGINX JavaScript 模块

NGINX JavaScript 可作为免费动态模块供 NGINX Plus 用户使用。有关加载说明,请参阅“NGINX Plus 管理指南”

为 NGINX 开源版加载 NGINX JavaScript 模块

NGINX JavaScript 模块默认包含在 NGINX Docker 官方镜像中。如果您的系统配置为使用 NGINX 开源版的官方预构建包,并且您安装的版本是 1.9.11 或更高版本,那么您可以安装 NGINX JavaScript 作为您平台的预构建包。

  1. 安装预构建包。

    • Ubuntu 和 Debian 系统:

      $ sudo apt-get install nginx-module-njs
    • RedHat、CentOS 和 Oracle Linux 系统:

      $ sudo yum install nginx-module-njs
  2. nginx.conf 配置文件的顶层(“main”)上下文(而非 httpstream 上下文)中添加一个 load_module 指令,以启用该模块。本例面向 HTTP 和 TCP/UDP 流量加载 JavaScript 模块。

    load_module modules/ngx_http_js_module.so;
    load_module modules/ngx_stream_js_module.so;
  3. 重新加载 NGINX Plus,以便将 NGINX JavaScript 模块加载到运行实例中。

    $ sudo nginx -s reload

将 NGINX JavaScript 编译为 NGINX 开源版的动态模块

如果您更喜欢从源码编译 NGINX 模块:

  1. 按照说明开源仓库构建 HTTP 和/或 TCP/UDP NGINX JavaScript 模块。
  2. 将模块二进制文件(ngx_http_js_module.songx_stream_js_module.so)复制到 NGINX 根目录的 modules 子目录(通常为 /etc/nginx/modules)。
  3. 执行“为 NGINX 开源版加载 NGINX JavaScript 模块”的第 2 步和第 3 步。
Hero image
免费白皮书:
NGINX 企阅版全解析

助力企业用户规避开源治理风险,应对开源使用挑战

关于作者

Liam Crilly

F5 产品管理总监

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。