精通 NJS HTTP 请求对象:全方位掌控 NGINX 请求生命周期

一、HTTP 请求对象核心定位

r 对象是 NJS 对单个 HTTP 请求的抽象,仅在 ngx_http_js_module 模块中可用(stream 模块无此对象)。其核心能力分为三类:

  1. 读取请求信息:解析 URL 参数、请求头、请求体、客户端地址等元数据;
  2. 操作响应内容:设置响应头、状态码、发送响应体、完成响应;
  3. 扩展请求能力:发起子请求、内部重定向、输出日志、操作 NGINX 变量。

版本注意:0.8.5 之前,r 对象的所有字符串属性均为字节字符串;0.8.0 移除了 r.requestBody/r.responseBody,需改用 r.requestBuffer/r.responseBuffer 等替代属性。

二、核心属性:读取请求元信息

2.1 URL 参数解析:r.args{}

r.args 是只读对象,自动解析 URL 查询字符串为键值对,核心特性:

  • 自动解码百分号编码(如 %322%20 → 空格);
  • 重复键返回数组,键名区分大小写;
  • 首次访问时才解析,性能友好。
基础示例
javascript 复制代码
// 请求 URL:/api?a=1&b=%32&A=3&b=4&B=two%20words
function parseArgs(r) {
    console.log(r.args.a); // 输出:"1"
    console.log(r.args.b); // 输出:["2", "4"](重复键)
    console.log(r.args.B); // 输出:"two words"(自动解码空格)
    console.log(r.args.unknown); // 输出:undefined
}
进阶用法
  • 仅获取单个参数 :优先使用 r.variables.arg_foo(NGINX 原生变量),性能更高,返回第一个值(不区分大小写,不解码);
  • 复杂参数解析 :结合 querystring 模块手动解析(适配特殊格式):
javascript 复制代码
import qs from 'querystring';

function advancedArgsParse(r) {
    // 手动解析原始参数(保留重复键、自定义解码规则)
    const rawArgs = qs.parse(r.variables.args);
    console.log(rawArgs); // 效果与 r.args 一致,但支持自定义解析选项
}

2.2 请求头与响应头:headersIn / headersOut

属性 类型 读写性 核心说明
r.headersIn Object 只读 客户端请求头,键名不区分大小写(headersIn.foo 等价于 headersIn['Foo']
r.headersOut Object 可写 响应头,需在发送响应头前设置(js_contentr.sendHeader()/r.return() 前,或 js_header_filter 中)
关键特性
  1. r.headersIn 特殊规则

    • 部分头(如 Content-TypeUser-Agent)仅保留单个值;
    • Cookie 重复值用分号分隔,其他头重复值用逗号分隔;
    • r.rawHeadersIn:返回原始头数组([[key, value], ...]),不合并重复头、不转小写:
    javascript 复制代码
    // 请求头:Host: localhost; Foo: bar; foo: bar2
    function getRawHeaders(r) {
        // 收集所有 Foo/foo 头的值
        const fooHeaders = r.rawHeadersIn
            .filter(v => v[0].toLowerCase() === 'foo')
            .map(v => v[1]);
        console.log(fooHeaders); // 输出:["bar", "bar2"]
    }
  2. r.headersOut 多值设置

    javascript 复制代码
    function setMultiHeaders(r) {
        // 设置多值响应头(输出两行 Foo: a / Foo: b)
        r.headersOut['Foo'] = ['a', 'b'];
        // 标准单值头(如 Content-Type)仅最后一个元素生效
        r.headersOut['Content-Type'] = ['text/plain', 'application/json']; // 最终为 application/json
        // 设置 Set-Cookie(始终返回数组)
        r.headersOut['Set-Cookie'] = ['sid=123', 'uid=456'];
    }

2.3 请求体与响应体:requestBuffer / responseBuffer

属性 说明 可用场景
r.requestBuffer 客户端请求体(Buffer 类型),未写入临时文件时可用 js_content 指令,需限制 client_max_body_size
r.requestText requestBuffer,但返回字符串(UTF-8 解码,无效字节替换为 �) 同上
r.responseBuffer 子请求响应体(Buffer 类型),大小受 subrequest_output_buffer_size 限制 子请求回调/异步处理
r.responseText responseBuffer,返回字符串 同上
示例:读取请求体
nginx 复制代码
# NGINX 配置:限制请求体大小,确保在内存中
http {
    client_max_body_size 1m;
    client_body_buffer_size 1m;
    js_import body.js;
    
    server {
        listen 80;
        location /submit {
            js_content body.readRequestBody;
        }
    }
}
javascript 复制代码
// body.js
function readRequestBody(r) {
    // 读取请求体为字符串
    const body = r.requestText;
    try {
        const data = JSON.parse(body);
        r.log(`解析请求体:${JSON.stringify(data)}`);
        r.return(200, `接收数据:${data.name}`);
    } catch (e) {
        r.error(`请求体解析失败:${e.message}`);
        r.return(400, "无效的 JSON 格式");
    }
}

export default { readRequestBody };

2.4 其他核心只读属性

属性 说明 示例
r.httpVersion HTTP 版本 "1.1"、"2.0"
r.internal 是否为内部请求 true(内部重定向)、false(客户端请求)
r.method HTTP 请求方法 "GET"、"POST"、"PUT"
r.remoteAddress 客户端 IP 地址 "192.168.1.100"
r.uri 标准化后的请求 URI "/api/user"(自动去除多余斜杠)
r.parent 父请求对象(子请求中可用) r.parent.method(获取父请求方法)

三、核心方法:操作请求与响应

3.1 响应发送:return() / send() / sendHeader()

方法 用途 示例
r.return(status[, body]) 发送完整响应(状态码+响应体) r.return(200, "success")r.return(302, "/login")
r.send(data) 发送部分响应体(需先调用 r.sendHeader() r.send("hello"); r.send(" world")
r.sendHeader() 发送响应头(需先设置 r.status/r.headersOut r.status=200; r.sendHeader();
r.finish() 完成响应发送(客户端断开连接) r.finish();
示例:分块发送响应
javascript 复制代码
function streamResponse(r) {
    // 设置响应头
    r.status = 200;
    r.headersOut['Content-Type'] = 'text/plain';
    r.sendHeader(); // 发送响应头
    
    // 分块发送响应体
    r.send("第一部分\n");
    r.send("第二部分\n");
    r.send("最后一部分");
    
    r.finish(); // 完成响应
}

3.2 子请求:r.subrequest()

发起内部子请求(访问 NGINX 其他 location),支持回调或 Promise 形式(0.3.8+),核心用途:

  • 聚合多个接口数据;
  • 访问内部接口获取配置/数据;
  • 异步处理后端请求。
异步示例:聚合子请求响应
javascript 复制代码
async function mergeSubrequest(r) {
    try {
        // 发起两个子请求
        const res1 = await r.subrequest('/api/data1', { method: 'GET' });
        const res2 = await r.subrequest('/api/data2', { 
            method: 'POST',
            body: JSON.stringify({ id: 1001 }),
            args: 'type=json'
        });
        
        // 解析子请求响应
        const data1 = JSON.parse(res1.responseText);
        const data2 = JSON.parse(res2.responseText);
        
        // 聚合数据并返回
        r.return(200, JSON.stringify({
            total: data1.value + data2.value,
            details: [data1, data2]
        }));
    } catch (e) {
        r.error(`子请求失败:${e.message}`);
        r.return(500, "内部服务错误");
    }
}

3.3 日志输出:log() / warn() / error()

向 NGINX 错误日志输出信息,不同方法对应不同日志级别,仅前 2048 字节会被记录

方法 日志级别 用途
r.log(string) info 普通日志(如请求信息、流程记录)
r.warn(string) warn 警告日志(如非致命错误、不兼容配置)
r.error(string) error 错误日志(如请求失败、参数异常)
示例
javascript 复制代码
function logRequest(r) {
    r.log(`客户端 ${r.remoteAddress} 发起 ${r.method} 请求:${r.uri}`);
    if (!r.args.id) {
        r.warn("缺少 id 参数");
    }
    if (r.requestBuffer.length > 1024 * 1024) {
        r.error(`请求体过大:${r.requestBuffer.length} 字节`);
        r.return(413, "请求体超过限制");
    }
}

3.4 内部重定向:r.internalRedirect()

将请求重定向到指定 URI/命名 location,重定向后启动新的 NJS VM,原 VM 停止,核心特性:

  • URI 以 @ 开头表示命名 location;
  • 重定向在当前 handler 执行完成后生效;
  • 保留 NGINX 变量,可传递数据。
示例:根据参数重定向
javascript 复制代码
function redirectByType(r) {
    const type = r.args.type || 'default';
    if (type === 'admin') {
        r.internalRedirect('@admin_location'); // 重定向到命名 location
    } else {
        r.internalRedirect('/api/default'); // 重定向到普通 URI
    }
}

3.5 NGINX 变量操作:r.variables{} / r.rawVariables{}

属性 类型 读写性 说明
r.variables Object 可写(部分变量) 操作 NGINX 变量(字符串类型),如 r.variables.request_id
r.rawVariables Object 可写 操作 NGINX 变量(Buffer 类型,0.5.0+)
关键规则
  1. 可写的变量类型:
    • js_var 指令定义的变量(0.5.3+);
    • NGINX 配置中引用过的变量;
  2. 内置变量(如 $http_*)通常不可写;
  3. 0.8.6+ 支持通过 r.variables[1] 访问正则捕获组。
示例
javascript 复制代码
function setVariable(r) {
    // 读取内置变量
    const requestId = r.variables.request_id;
    r.log(`请求 ID:${requestId}`);
    
    // 设置自定义变量(需先在 nginx.conf 定义:js_var $custom_flag;)
    r.variables.custom_flag = 'true';
    
    // 访问正则捕获组(假设 location 配置:/api/(\d+)/)
    const userId = r.variables[1];
    r.return(200, `用户 ID:${userId}`);
}

3.6 异步 handler 返回值:r.setReturnValue()

0.7.0+ 新增,用于异步 js_set handler 设置返回值(普通 return 无法在异步函数中生效):

javascript 复制代码
import crypto from 'crypto';

async function calcHash(r) {
    // 异步计算哈希
    const digest = await crypto.subtle.digest('SHA-256', r.headersIn.host);
    r.setReturnValue(digest); // 设置 js_set 变量的值
}

四、过滤器专用方法:body_filter 场景

以下方法仅在 js_body_filter 指令中可用,用于处理响应体分块:

方法 说明
r.done() 后续响应块直接发送给客户端,不再调用 js_body_filter
r.sendBuffer(data[, options]) 将数据添加到响应块链,支持 last(是否最后一块)、flush(刷新缓冲区)
示例:改写响应体
javascript 复制代码
function rewriteBody(r, data, flags) {
    // 将响应体中的 "old" 替换为 "new"
    const newData = data.toString().replace(/old/g, 'new');
    r.sendBuffer(newData, { last: flags.last });
    
    // 若数据包含 "stop",停止后续处理
    if (newData.includes('stop')) {
        r.done();
    }
}

五、版本兼容与最佳实践

5.1 核心版本兼容要点

  1. 0.8.0 及以上:
    • 移除 r.requestBody/r.responseBody,改用 r.requestBuffer/r.responseBuffer
    • r.requestText/r.responseText 替代字符串形式的请求体/响应体;
  2. 0.8.5 及以上:
    • r 对象字符串属性为 UTF-8 字符串(而非字节字符串);
  3. 0.8.6 及以上:
    • 支持 r.variables[1] 访问正则捕获组;
  4. 0.3.8 及以上:
    • r.subrequest() 支持 Promise 形式(无需回调)。

5.2 最佳实践

  1. 性能优化
    • 仅在需要时访问 r.args(首次访问才解析);
    • 单个参数优先使用 r.variables.arg_foo 而非 r.args.foo
    • 限制请求体大小(client_max_body_size),避免 r.requestBuffer 写入临时文件;
  2. 安全性
    • 解析请求体前校验 Content-Type,避免恶意数据;
    • 日志输出敏感信息前脱敏(如手机号、身份证号);
  3. 可维护性
    • 异步逻辑优先使用 async/awaitr.subrequest() Promise 形式);
    • 响应头设置需在 r.sendHeader()/r.return() 前完成,避免失效。

六、总结

  1. NJS HTTP 请求对象 r 是掌控请求生命周期的核心,涵盖参数解析、头信息操作、子请求、响应发送等全能力;
  2. 核心属性中,r.args 解析 URL 参数、r.headersIn/r.headersOut 操作头信息、r.requestBuffer 读取请求体是高频使用点;
  3. 核心方法中,r.return() 快速响应、r.subrequest() 异步聚合、r.internalRedirect() 内部重定向是网关层常用能力;
  4. 使用时需注意版本兼容(如 0.8.0 移除的属性、0.8.5 字符串编码变化),并遵循性能优化与安全最佳实践。
相关推荐
飞翔沫沫情9 小时前
Nginx运维维护规范及全配置详解【持续更新】
nginx·nginx 配置·nginx 操作手册·nginx 使用规范·nginx 日志规范·nginx 配置文件说明
deriva9 小时前
nginx如何将某域名/二级站点/代理到二级站点?以ChirpStack实战为例
运维·nginx
睡不醒的猪儿20 小时前
nginx常见的优化配置
运维·nginx
root666/1 天前
【后端开发-nginx】proxy_pass和proxy_redirect参数作用
运维·nginx
工具罗某人1 天前
docker快速部署kafka
java·nginx·docker
864记忆2 天前
Qt创建连接注意事项
数据库·qt·nginx
Anarkh_Lee2 天前
别再手写 conf 了!NgxFlow:基于 React Flow 的 Nginx 可视化与调试神器
前端·nginx·数据可视化
Run Out Of Brain2 天前
解决nginx代理配置下wordpress的 /wp-admin/返回 302 重定向到登录页问题
运维·nginx
一勺菠萝丶2 天前
芋道项目部署:前端写死后端地址 vs Nginx 反向代理
前端·nginx·状态模式