当JS邂逅Nginx

当JS邂逅Nginx

当JS邂逅Nginx

E1NYpQaXEAAS1GZ.jpeg

由于许多 web 开发并不熟悉 lua 语言. 因此 Nginx 推出了 NJS 模块, 可以在 Nginx 的配置中引入 js 脚本, 从而实现一些更复杂的 Nginx 配置功能.

一 安装 NJS 模块

要求 Nginx 的版本大于 1.9.11, 因为从该版本才开始支持 load_module 指令

方法一: 动态加载 NJS 模块

注意: 不同版本的 Nginx 需要相应版本的 NJS 模块.

  1. 将 ngx_http_js_module.so 文件放在Nginx 根目录的 modules 目录下,
  2. 在 Nginx.conf 中增加引入模块
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;
复制代码

方法二: 编译时增加模块

  1. 下载源码:

该仓库在mercurial中管理, 需要使用 hg 命令下载源码hg clone http://hg.nginx.org/NJS

  1. Nginx 编译时增加如下配置
./configure --add-module=<path to NJS>/NJS/nginx
复制代码

二 NJS模块运行环境的特点​

NJS 模块并不是运行一个 Nodejs, 因此 Nginx JS 只能像 lua 模块一样作为 Nginx 的一个中间件, 无法独立作为一个完整的后台服务.

1. 每次请求时创建运行时环境, 请求结束时销毁

2. 非阻塞代码执行

NJS 采用事件驱动模型对 NJS 运行时环境进行调度。当 NJS 执行阻塞操作(例如读取网络数据或发出外部子请求)时,Nginx 会挂起当前 NJS VM 的执行,并在事件完成时重新调度。因此 NJS 的代码可以以简单的线性方式来写

3. 只支持部分 ECAMA 规范的语法

NJS 基于ECMAScript 5.1 规范, 并支持 ECMAScript 6 中的部分函数 支持的语法列表nginx.org/en/docs/NJS…

4. 紧密集成请求处理过程

Nginx 对请求的处理包含多个阶段. Nginx的指令通常在某个指定的阶运行对请求进行处理. Nginx 的模块也正是利用这个能力, 来调试或修改一个请求. NJS 模块也是通过指令的形式, 实现在特定的阶段运行 js 代码逻辑.

三 NJS 模块支持的指令及对应的处理阶段

处理阶段HTTP 模块Stream 模块
Access – Authentication and access controlauth_request and js_contentjs_access
Pre-read – Read/write payloadN/Ajs_preread
Filter – Read/write response during proxyjs_body_filter js_header_filterjs_filter
Content – Send response to clientjs_contentN/A
Log / Variables – Evaluated on demandjs_setjs_set

四 NJS 的简单用法示例

以下示例用 js 定义一种 log 的格式

在 Nginx 配置目录下创建一个 logging.js 文件

// 文件位置: [nginx根目录]/conf/logging.js
// 文件内容: 解析请求, 打印出所有的请求头
function logAllHeaders(r) {
    var log = `${r.variables.time_iso8601} client=${r.remoteAddress} method=${r.method} uri=${r.uri} status=${r.status}`;
    r.rawHeadersIn.forEach(h => log += ` in.${h[0]}=${h[1]}`);
    r.rawHeadersOut.forEach(h => log += ` out.${h[0]}=${h[1]}`);
    return log;
}

export default { logAllHeaders }
复制代码

# Nginx 的配置文件

http {
   js_import  logging.js;      # js_import 加载一个 js 脚本, 该文件放在Nginx 配置文件的目录下. js 的文件名会作为该模块的命名空间. 引用函数时可以通过[文件名].[函数名]的方式来引用
   js_set     $log_all_headers logging.logAllHeaders; # js_set 把js文件中的函数 logAllHeaders 的输出保存到变量 $log_all_headers.
   log_format kvpairs $log_all_headers;          # 自定义一种日志格式 kvpairs
	
    server {
        listen 80;
        access_log /var/log/nginx/access.log kvpairs; # 设置该规则下的日志格式为上面自定义的格式
        root /usr/share/nginx/html;
    }
}
复制代码

五 NJS 支持的指令

参考文档

NJS 支持的指令并不多. 要实现复杂的功能需要与 Nginx 的其他指令结合一起使用.

以下介绍几个常用的指令

js_body_filter 修改 response 的 body

Syntax:	js_body_filter function | module.function [buffer_type=string | buffer];
Default:Context:	location, limit_except
This directive appeared in version 0.5.2.
复制代码

示例

/**
* 处理 response body 的函数
* @param { object } r - http 对象
* @param { buffer_type } data - 请求的 body 的数据
* @param { boolean } flags - 是否是最后一个数据块
*/
function filter(r, data, flags) {
    r.sendBuffer(data.toLowerCase(), flags);
}
复制代码

js_content 处理请求的返回

Syntax:	js_content function | module.function;
Default:Context:	location, limit_except
复制代码

示例

http {
    # 引入 js 模块
    js_import  http.js;                 
    server {
        listen 80;
        location /content {
            # 通过 js_content 指令指定要执行的 js 函数
            js_content http.content;
        }
    }
}

复制代码
// http.js 文件
function content(r) {
    r.status = 200;
    r.headersOut['Content-Type'] = "text/plain; charset=utf-8";
    r.headersOut['Content-Length'] = 12;
    r.sendHeader();
    r.send("I am content");
    r.finish()
}

export default { content }
复制代码

js_header_filter 修改返回的请求头

Syntax:	js_header_filter function | module.function;
Default:Context:	location, limit_except
This directive appeared in version 0.5.1.
复制代码

js_import 导入一个 js 文件

Syntax:	js_import module.js | export_name from module.js;
Default:Context:	http
This directive appeared in version 0.4.0.
复制代码

示例

http {
    # 引入 js 模块. 文件名会作为该模块的命名空间. 引用函数时可以通过[文件名].[函数名]的方式来引用
    js_import  http.js;                 
    server {
        listen 80;
        location /content {
            # 通过 js_content 指令指定要执行的 js 函数
            js_content http.content;
        }
    }
}
复制代码

js_set 设置变量

Syntax:	js_set $variable function | module.function;
Default:Context:	http
复制代码

该指令引用的函数会在变量第一次被引用时执行. 并且函数内仅支持同步的操作

广告时间

微信数据中心目前有少量前端 HC, 主要负责微信大数据相关的系统开发, 如调度, BI, 可视化等. 欢迎有兴趣的同学联系 zhichaoxu@tencent.com

参考资料

分类:
前端
标签: