博客 | NGINX

使用 NGINX JavaScript 模块充分利用 JavaScript 的强大功能和便利性

NGINX-F5-horiz-black-type-RGB 的一部分
Liam Crilly 缩略图
利亚姆·克里利
2021 年 4 月 23 日发布

编辑– 题为“NGINX JavaScript 模块简介”的博客文章重定向至此处。 该帖子已更新截至 2021 年 4 月支持的 NGINX JavaScript 模块指令和功能。

NGINX JavaScript 模块 (njs) 在 NGINX Open Source 1.11.10 和NGINX Plus R12中作为稳定模块全面可用。 [该模块最初称为 nginScript,该名称出现在一些较旧的帖子中。]2015 年 9 月推出以来,我们一直在稳步开发 NGINX JavaScript,添加了稳定模块中包含的功能和语言支持。

NGINX JavaScript 是 NGINX 和 NGINX Plus 的独特 JavaScript 实现,专为服务器端用例和每个请求处理而设计。 它使用 JavaScript 代码扩展 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 的目标是通过使用流行的编程语言为尽可能广泛的社区提供编程配置解决方案。

NGINX JavaScript 不是 Node.js

NGINX JavaScript 的目的并不是将 NGINX 或 NGINX Plus 变成应用服务器。 简单来说,NGINX JavaScript 的用例类似于中间件,因为 JavaScript 代码的执行发生在客户端和内容之间。 从技术上讲,虽然 Node.js 与 NGINX JavaScript 和 NGINX 或 NGINX Plus 的组合有两点相同——事件驱动架构和 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 模块通常利用在特定阶段检查或修改请求的能力。 NGINX JavaScript 通过配置指令公开一些处理阶段,以控制 JavaScript 代码的执行时间。 这种与配置语法的集成保证了原生 NGINX 模块的强大功能和灵活性以及 JavaScript 代码的简单性。

    下表显示了在撰写本文时可以通过 NGINX JavaScript 访问哪些处理阶段,以及提供它的配置指令。

    处理阶段 HTTP 模块 流模块
    访问——身份验证和访问控制 auth_requestjs_content js_access
    预读– 读/写有效载荷 不适用 js_预读
    过滤器– 代理期间的读/写响应 js_body_filter
    js_header_filter
    js_filter
    内容– 向客户端发送响应 js_内容 不适用
    日志/变量– 按需评估 js_set js_set

NGINX JavaScript 入门——一个真实的例子

NGINX JavaScript 作为模块实现,您可以将其编译为 NGINX 开源二进制文件或动态加载到 NGINX 或 NGINX Plus 中。 启用 NGINX JavaScript 的说明 NGINX 和 NGINX Plus 出现在本文末尾。

在此示例中,我们使用 NGINX 或 NGINX Plus 作为简单的反向代理,并使用 NGINX JavaScript 以特殊格式构建访问日志条目,如下所示:

  • 包含客户端发送的请求标头
  • 包含后端返回的响应头
  • 使用键值对,通过 ELK Stack(现称为 Elastic Stack)、Graylog 和 Splunk 等日志处理工具高效提取和搜索数据

本示例的 NGINX 配置非常简单。

 

如您所见,NGINX JavaScript 代码与配置语法不一致。 相反,我们使用js_import指令来指定包含所有 JavaScript 代码的文件。 js_set指令定义了一个新的 NGINX 变量$access_log_headers ,以及填充它的 JavaScript 函数。 log_format指令定义了一种名为kvpairs的新格式,它使用$access_log_headers的值写入每行日志。

服务器块定义了一个简单的 HTTP 反向代理,将所有请求转发到https://www.example.comaccess_log指令指定所有请求都将以kvpairs格式记录。

现在让我们看一下准备日志条目的 JavaScript 代码。

 

kvAccess函数的返回值(日志条目)被传递给rawheader_logging.conf中的js_set配置指令。 请记住,NGINX 变量是根据需要进行评估的,这反过来意味着当需要变量的值时,会执行js_set定义的 JavaScript 函数。 在此示例中, $access_log_headerslog_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 客户端=172.17.0.1 方法=GET uri=/index.html 状态=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应用)利用具有其自己的一组属性的会话对象。 有关 HTTP 和 TCP/UDP 的 NGINX JavaScript 解决方案的其他示例,请参阅NGINX JavaScript 模块的用例

我们很乐意听到您为 NGINX JavaScript 提出的用例 - 请在下面的评论部分告诉我们。


NGINX JavaScript 模块的用例

查看以下博客文章,探索 NGINX JavaScript 模块的其他 HTTP 和 TCP/UDP 用例:

 


[ngx_snippet 名称=’njs-enable-instructions’]


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”