下面给你一份 超详尽的 njs(NGINX JavaScript 引擎)入门 + 进阶教程,从背景、原理、安装、核心 API、实战示例、注意事项等全面覆盖。你可以把它当作自己的备忘手册。

(注:本文参考官方文档与社区资料)


目录

  1. 背景与定位
  2. 支持环境与版本兼容
  3. 安装与启用
  4. 配置简介
  5. 核心模型与执行流程
  6. njs API & 对象模型
  7. 示例:从简单到复杂
  8. 高级特性
  9. 性能与资源注意点
  10. 调试与测试
  11. 社区资源与示例项目
  12. 小结 + 使用建议

1. 背景与定位

  • 什么是 njs?
    njs 是 NGINX 提供的一个 JavaScript 引擎模块,允许在 nginx 配置中嵌入 JavaScript 脚本,对 HTTP / Stream 流程进行扩展和 “编程式控制”。(nginx.org)
    换句话说,njs 就像一个轻量级的 JS 运行时,内嵌在 nginx 的 worker 里,用来处理请求、响应、过滤、hook 等逻辑。(GitHub)
  • 它能做什么?为什么要用它?
    • 在请求达到后端之前做安全校验、访问控制
    • 动态修改响应头、响应体
    • 实现自定义 content handler(响应生成)
    • 在 stream / TCP / UDP 流中插入 JS 逻辑
    • 执行异步请求(fetch / HTTP 调用)
    • 实现过滤、路由、度量、缓存等
    • 与 NGINX 自身指令结合:可以更灵活地做控制,而不用修改 C 模块
  • 与传统方式的区别
    通常要做这些功能,你要写 nginx 模块(用 C)、用 Lua(OpenResty)、或在上层业务里做。njs 能让很多逻辑直接在 nginx 层完成、减少网络往返、加快处理。

2. 支持环境与版本兼容

  • njs 是作为 nginx 的 动态模块 提供,可插入到 nginx Open Source 或 NGINX Plus 中。(GitHub)
  • v0.8.6 起,支持选择不同的 JavaScript 引擎(默认 njs 引擎,也支持 QuickJS)(nginx.org)
  • 兼容性注意事项:
    • njs 语言是 JavaScript 的子集,基于 ES5,带有一些 ES6/更高特性(视版本)(GitHub)
    • 在某些版本中,部分 API(如 require())被弃用或限制,推荐使用 import 模块语法。(nginx.org)
    • 在 QuickJS 模式下,有些 njs 特有 API 不可用(如 njs.dump()、console.dump())(nginx.org)
    • 新版本中增加对 process, TextEncoder / TextDecoder 等的支持(nginx.org)

3. 安装与启用

安装

不同系统下安装方式略有不同。以 Linux 为例:

  • Ubuntu / Debian:
sudo apt update
sudo apt install nginx-module-njs

  • RHEL / CentOS / RedHat:
sudo yum install nginx-module-njs

或在某些发行版上使用 dnf。(GitHub)

  • 在 Alpine 或较轻量系统也有对应包。(docs.nginx.com)
  • 也可以从源代码编译,作为 nginx 的动态模块加入,或编译为 CLI 使用。(GitHub)

启用 / 加载模块

在 nginx 配置文件的最顶级(main)上下文,加载模块:

load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;

然后重载 nginx。(docs.nginx.com)

确认配置:

nginx -t
nginx -s reload


4. 配置简介 + 指令

njs 在 nginx 配置中通过一些指令来连接 JS 逻辑与请求处理流程。以下是一些核心指令。

指令上下文说明 / 用法版本 / 备注
js_import <module>http / server / location导入 JS 模块文件以供使用基础用法
js_content module.functionlocation将某个 JS 函数作为内容处理 handler使用在 HTTP 模式
js_setlocation用 JS 表达式设置一个 nginx 变量
js_body_filter / js_header_filterlocation使用 JS 逻辑来过滤响应体 / 响应头
js_prereadstream / TCP 上下文在接收到数据前执行 JS 处理
`js_engine <njsqjs>`http / server / location选择 JS 引擎:njs 或 QuickJS
js_context_reuse <number>http / server / location设置 JS 上下文重用数量(针对 QuickJS 引擎)v0.8.6+ (nginx.org)
js_fetch_* 系列指令http / server / location控制 njs 中 fetch 功能的 buffer 大小、超时、协议等(nginx.org)
js_path <path>http / server / location设置额外的 JS 模块路径(nginx.org)
js_periodic module.func [interval=..] [jitter=..] [worker_affinity=..]location定期在 worker 进程中执行某个 JS 函数v0.8.1+ (nginx.org)

示例(简化):

http {
    load_module modules/ngx_http_js_module.so;
    load_module modules/ngx_stream_js_module.so;

    js_import mymod from /etc/nginx/njs/mymod.js;

    server {
        listen 80;
        location /hello {
            js_content mymod.hello;
        }
    }
}


5. 核心模型与执行流程

要真正用好 njs,理解其执行模型与请求生命周期中的挂钩点很关键。

请求流程中的挂钩点

以 HTTP 为例(stream 模式类似):

  1. 客户端发起请求
  2. nginx 核心处理请求,按 location 匹配
  3. 如果该 location 配置了 js_content,就进入 njs 逻辑,由 JS 函数生成响应
  4. JS 函数可以访问 r(request/session)对象,读取请求参数、头、body 等
  5. JS 可以决定直接返回(r.return()),也可继续委托 upstream
  6. 在响应过程中,还可以在 js_header_filterjs_body_filter 中对响应头/体做处理
  7. 完成后,返回响应给客户端

在 stream / TCP / UDP 模式下,类似流程也可在 js_preread 阶段插入逻辑。

上下文与状态

  • 每个请求由一个 JS 上下文(VM 实例或环境)处理
  • 为了性能,nginx 会重用上下文(context reuse),减少创建销毁开销(特别在 QuickJS 引擎下)(nginx.org)
  • JS 模块可以导出多个函数,nginx 配置中引用它们
  • 在 JS 里,对 r(request/session 对象)、r.variables(nginx 变量)、r.args(GET 参数对象)等有访问能力

6. njs API & 对象模型

下面列出一些核心 API、对象、属性和方法(不可能穷尽,建议结合官方 Reference 查文档)(nginx.org)

r 对象(HTTP Request / Session 对象)

这是 JS 脚本中最常用的对象,代表当前请求会话。

常见属性 / 方法:

属性 / 方法说明
r.args请求的 query 参数对象,重复键以数组形式表示
r.variablesnginx 变量映射($uri, $args 等)
r.headersIn请求头映射对象
r.headersOut响应头映射,设置响应头
r.methodHTTP 方法,如 “GET”, “POST”
r.requestBody请求体(如果已读取)
r.return(status, body?)终止当前请求,立即返回响应
r.error(msg)在日志系统里写错误
r.done()在 body_filter 中,中断后续过滤流程
r.subrequest() / r.internalRedirect()发起子请求或内部重定向
r.log() / r.warn() / r.debug()日志相关函数

其他模块 / API

  • Fetch API / HTTP:njs 支持类似 fetch 的网络请求能力,可以在 JS 中发起 HTTP 调用(对 upstream 或第三方服务)
  • Shared Dictionary(共享字典):多个请求之间共享数据存储(键/值)
  • 定时 / 周期任务js_periodic 指令
  • 模块 / import:可以把 JS 代码拆分模块,通过 js_import 导入
  • 模块路径 & 模块查找:通过 js_path 指定额外路径
  • QuickJS 支持特性:BigInt、Proxy、异步生成器等(视版本支持)(nginx.org)
  • njs 内部 / 辅助 API:如 njs.dump(), console.dump() 等(部分版本)(nginx.org)

举例:读取 GET 参数并返回

export default {
  hello: function (r) {
    let name = r.args.name || "world";
    r.return(200, "Hello " + name);
  }
}

然后 nginx 配置:

js_import mymod from /etc/nginx/njs/mymod.js;
server {
  location /hello {
    js_content mymod.hello;
  }
}


7. 示例:从简单到复杂

下面按层级给你几个示例,帮助加深理解。

示例 1:Hello World

JS 脚本 hello.js

export default {
  hello: function(r) {
    r.return(200, "Hello, njs!");
  }
}

nginx 配置:

load_module modules/ngx_http_js_module.so;

http {
  js_import hello from /etc/nginx/njs/hello.js;

  server {
    listen 8080;
    location /hello {
      js_content hello.hello;
    }
  }
}

访问 http://your-server:8080/hello → 显示 “Hello, njs!”


示例 2:参数处理 + JSON 返回

export default {
  greet: function(r) {
    let name = r.args.name || "Guest";
    let obj = { message: "Hello " + name, time: Date.now() };
    r.headersOut['Content-Type'] = 'application/json';
    r.return(200, JSON.stringify(obj));
  }
}

配置中:

location /greet {
    js_content greet.greet;
}

请求如 /greet?name=Alice → 返回:

{"message":"Hello Alice","time":167...}


示例 3:响应头 / 响应体过滤

假设你希望在返回内容中插入一个 footer 或做修改,可以用 js_body_filter

export default {
  filter: function(r, data, flags) {
    // data 是 Buffer / 字符串片段
    // 最终返回修改后的数据
    let content = data.toString();
    content = content + "\n&lt;!-- Served by njs -->";
    return content;
  }
}

nginx 配置:

location /static {
    js_header_filter filter.filter;
    js_body_filter filter.filter;
    proxy_pass http://backend;
}

这样,所有经过这个 location 的响应体都会被 append 一个注释。


示例 4:子请求(subrequest)

一种强大用法是在 JS 里发起子请求:

export default {
  doSomething: function(r) {
    r.subrequest('/api/data', function(res) {
        if (res.status != 200) {
            r.return(500, 'error upstream');
        } else {
            r.return(200, "Got data: " + res.responseBody);
        }
    });
  }
}

在 nginx:

location /do {
    js_content mymod.doSomething;
}
location /api/data {
    proxy_pass http://backend/api/data;
}

这样你可以在一个请求里整合后端多个子服务数据。


8. 高级特性

下面这些特性适合中高级用法:

  • 周期定时任务(js_periodic)
    可以在后台定时执行 JS 任务(无请求触发)(nginx.org)
  • 共享字典 / 缓存
    多个请求可以共享一个字典存储(key-value),用于计数、缓存、状态保持
  • 引擎切换 / QuickJS 支持
    通过 js_engine qjs 切换到 QuickJS 模式,可以获取更现代 JS 特性(如 BigInt、Proxy 等)(nginx.org)
  • 上下文重用
    在 QuickJS 模式下,可以通过 js_context_reuse 设置 JS 上下文对象池数量,减少创建开销。(nginx.org)
  • 动态模块导入 / 多模块组织
    使用 js_import + ES 模块方式组织 JS 代码,使逻辑清晰可维护
  • 异步 / Promise 支持(取决版本)
    部分版本支持异步 JS 特性,例如异步函数或 Promise(视 njs 版本支持情况)
  • 安全 / 沙箱机制
    njs 默认限制了一些全局 API,只暴露给 JS 与 nginx 接口。不能像 Node 那样任意访问文件系统等。

9. 性能与资源注意点

njs 虽然灵活,但也有一些资源与性能方面的考量:

  • 每个请求创建 / 销毁 JS 上下文代价大 → 使用上下文重用
  • JS 执行时间要短,避免阻塞 nginx worker
  • JS 逻辑复杂时,可能影响吞吐与延迟
  • 注意内存泄漏、闭包、全局变量滥用
  • 在高并发场景下,尽量减少 JS 调用的次数
  • 避免在 JS 里做大量 CPU 密集型计算
  • 使用 js_periodic、共享字典等功能时,要控制资源消耗

10. 调试与测试

  • njs 提供一个命令行工具 njs(CLI),可以在本地执行 JS 代码、调试语法错误(不含 nginx 对象)(GitHub)
  • 在 JS 里使用 r.log(), r.error(), r.debug() 输出到 nginx 日志,便于调试
  • 在 nginx 配置里开启错误日志级别为 debug 或 info
  • 使用示例项目 + Docker 方式快速试验
  • 单元测试 / 模块测试:把 JS 模块抽出,单独在 node 或 CLI 环境测试逻辑

11. 社区资源与示例项目

  • 官方 njs 文档(Reference)(nginx.org)
  • njs GitHub 仓库(源码 + 文档)(GitHub)
  • njs-examples 仓库,包含很多实用样例:HTTP、stream、授权、加密等(GitHub)
  • 社区博客 / 入门教程(如 “Create Nginx extensions in JavaScript”)(DEV Community)
  • 视频教程(官方 + 社区)(YouTube)

12. 小结 + 使用建议

  • njs 是一个非常强大的工具,适合在 nginx 层处理一些轻量逻辑,但不适合用作完整应用逻辑
  • 从简单的 js_content 入手,一步步扩展到过滤、子请求、共享字典等
  • 在性能敏感场景下,要格外注意 JS 执行成本
  • 推荐把复杂逻辑保留给后端服务,把 njs 用作 “辅助 / 边缘处理”
  • 开发时多用日志 + CLI 工具 + 示例项目辅助调试