借助NGINX JavaScript模块,为每个请求充分发挥JavaScript的强大功能与便利性

NGINX | 2021年4月23日

编者:题为“NGINX JavaScript模块简介”的博文现已重定向至此。本文已更新,涵盖截至2021年4月NGINX JavaScript Module支持的指令和功能。

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,其功能达到与Lua相当的水平尚需时日。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则是专为NGINX和NGINX Plus设计的ECMAScript标准定制实现。Node.js在内存中保留一个持久运行的JavaScript虚拟机(VM),并通过定期执行垃圾回收来管理内存;而NGINX JavaScript会为每个请求初始化一个新的JavaScript VM并分配所需内存,在请求完成后释放这些内存。

作为服务器端语言的JavaScript

如上所述,NGINX JavaScript是JavaScript语言的定制实现。现有的其他JavaScript运行时引擎均设计为在Web浏览器中执行。客户端代码与服务器端代码的执行方式在许多方面有所不同,包括系统资源的可用性以及可能并发运行的运行时实例数量。

为了满足服务器端代码执行的要求,并与NGINX的请求处理架构顺畅集成,我们决定自行实现JavaScript运行时。NGINX JavaScript的设计原则如下:

  • 运行时环境与请求共存亡NGINX JavaScript模块采用单线程字节码执行,旨在快速完成初始化和销毁。每个请求都会初始化一个运行时环境。由于无需初始化复杂状态或辅助程序,启动速度极快。执行期间,内存会在内存池中累积;执行完成后,通过释放内存池来释放内存。这种内存管理机制无需跟踪和释放单个对象,也无需使用垃圾回收器。
  • 非阻塞代码执行NGINX和NGINX Plus采用事件驱动模型,调度各个NGINX JavaScript运行时环境的执行。当NGINX JavaScript规则执行阻塞操作(例如读取网络数据或发出外部子请求)时,NGINX和NGINX Plus会以透明方式暂停相关NGINX JavaScript VM的执行,并在事件完成后重新调度该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 模块Stream 模块
Access ;认证和访问控制auth_request 和 js_contentjs_access
Pre-read ;读取/写入有效负载不适用js_preread
Filter ;在代理期间读取/写入响应js_body_filter
js_header_filter
js_filter
Content ;向客户端发送响应js_content不适用
Log / Variables ;按需求值js_setjs_set

NGINX JavaScript入门:真实场景示例

NGINX JavaScript以模块形式实现,可将其编译到NGINX Open Source二进制文件中,也可将其动态加载到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的值写入每一行日志。

The server块定义了一个简单的HTTP反向代理,将所有请求转发到https://www.example.com。access_log指令指定使用kvpairs格式记录所有请求。

现在来看一下用于准备日志条目的JavaScript代码。

正在加载代码示例…

The return value from the kvAccess函数的返回值(即一条日志条目)会传递给js_set配置指令,该指令位于rawheader_logging.conf中。请注意,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.log2021-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-264x22 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 模块的用例

阅读以下博客文章,了解NGINX JavaScript模块在HTTP和TCP/UDP方面的其他用例:

[ngx_snippet name=’njs-enable-instructions’]


分享
标签: F5 NGINX, 2021, Tech

关于作者

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。