一、HTTP 请求对象核心定位
r 对象是 NJS 对单个 HTTP 请求的抽象,仅在 ngx_http_js_module 模块中可用(stream 模块无此对象)。其核心能力分为三类:
- 读取请求信息:解析 URL 参数、请求头、请求体、客户端地址等元数据;
- 操作响应内容:设置响应头、状态码、发送响应体、完成响应;
- 扩展请求能力:发起子请求、内部重定向、输出日志、操作 NGINX 变量。
版本注意:0.8.5 之前,
r对象的所有字符串属性均为字节字符串;0.8.0 移除了r.requestBody/r.responseBody,需改用r.requestBuffer/r.responseBuffer等替代属性。
二、核心属性:读取请求元信息
2.1 URL 参数解析:r.args{}
r.args 是只读对象,自动解析 URL 查询字符串为键值对,核心特性:
- 自动解码百分号编码(如
%32→2、%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_content 中 r.sendHeader()/r.return() 前,或 js_header_filter 中) |
关键特性
-
r.headersIn 特殊规则:
- 部分头(如
Content-Type、User-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"] } - 部分头(如
-
r.headersOut 多值设置:
javascriptfunction 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+) |
关键规则
- 可写的变量类型:
js_var指令定义的变量(0.5.3+);- NGINX 配置中引用过的变量;
- 内置变量(如
$http_*)通常不可写; - 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 核心版本兼容要点
- 0.8.0 及以上:
- 移除
r.requestBody/r.responseBody,改用r.requestBuffer/r.responseBuffer; r.requestText/r.responseText替代字符串形式的请求体/响应体;
- 移除
- 0.8.5 及以上:
r对象字符串属性为 UTF-8 字符串(而非字节字符串);
- 0.8.6 及以上:
- 支持
r.variables[1]访问正则捕获组;
- 支持
- 0.3.8 及以上:
r.subrequest()支持 Promise 形式(无需回调)。
5.2 最佳实践
- 性能优化 :
- 仅在需要时访问
r.args(首次访问才解析); - 单个参数优先使用
r.variables.arg_foo而非r.args.foo; - 限制请求体大小(
client_max_body_size),避免r.requestBuffer写入临时文件;
- 仅在需要时访问
- 安全性 :
- 解析请求体前校验 Content-Type,避免恶意数据;
- 日志输出敏感信息前脱敏(如手机号、身份证号);
- 可维护性 :
- 异步逻辑优先使用
async/await(r.subrequest()Promise 形式); - 响应头设置需在
r.sendHeader()/r.return()前完成,避免失效。
- 异步逻辑优先使用
六、总结
- NJS HTTP 请求对象
r是掌控请求生命周期的核心,涵盖参数解析、头信息操作、子请求、响应发送等全能力; - 核心属性中,
r.args解析 URL 参数、r.headersIn/r.headersOut操作头信息、r.requestBuffer读取请求体是高频使用点; - 核心方法中,
r.return()快速响应、r.subrequest()异步聚合、r.internalRedirect()内部重定向是网关层常用能力; - 使用时需注意版本兼容(如 0.8.0 移除的属性、0.8.5 字符串编码变化),并遵循性能优化与安全最佳实践。