ngx_conf_handler
的核心作用指令路由:根据指令名找到对应的模块和。上下文校验:确保指令用在允许的配置块中(如httpserver语法校验块指令必须有;普通指令必须有;;参数数量必须合法。配置定位:确定该指令的值应写入哪个配置结构体(main/srv/loc)。调用处理函数:执行模块注册的set函数完成实际配置。错误反馈:提供清晰的错误信息(未知指令、位置错误、参数错误等)。这是 Nginx高度模块化、可扩展配置系统
1. 定义
在./nginx-1.24.0/src/core/ngx_conf_file.c
static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
char *rv;
void *conf, **confp;
ngx_uint_t i, found;
ngx_str_t *name;
ngx_command_t *cmd;
name = cf->args->elts;
found = 0;
for (i = 0; cf->cycle->modules[i]; i++) {
cmd = cf->cycle->modules[i]->commands;
if (cmd == NULL) {
continue;
}
for ( /* void */ ; cmd->name.len; cmd++) {
if (name->len != cmd->name.len) {
continue;
}
if (ngx_strcmp(name->data, cmd->name.data) != 0) {
continue;
}
found = 1;
if (cf->cycle->modules[i]->type != NGX_CONF_MODULE
&& cf->cycle->modules[i]->type != cf->module_type)
{
continue;
}
/* is the directive's location right ? */
if (!(cmd->type & cf->cmd_type)) {
continue;
}
if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"directive \"%s\" is not terminated by \";\"",
name->data);
return NGX_ERROR;
}
if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"directive \"%s\" has no opening \"{\"",
name->data);
return NGX_ERROR;
}
/* is the directive's argument count right ? */
if (!(cmd->type & NGX_CONF_ANY)) {
if (cmd->type & NGX_CONF_FLAG) {
if (cf->args->nelts != 2) {
goto invalid;
}
} else if (cmd->type & NGX_CONF_1MORE) {
if (cf->args->nelts < 2) {
goto invalid;
}
} else if (cmd->type & NGX_CONF_2MORE) {
if (cf->args->nelts < 3) {
goto invalid;
}
} else if (cf->args->nelts > NGX_CONF_MAX_ARGS) {
goto invalid;
} else if (!(cmd->type & argument_number[cf->args->nelts - 1]))
{
goto invalid;
}
}
/* set up the directive's configuration context */
conf = NULL;
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);
} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[cf->cycle->modules[i]->ctx_index];
}
}
rv = cmd->set(cf, cmd, conf);
if (rv == NGX_CONF_OK) {
return NGX_OK;
}
if (rv == NGX_CONF_ERROR) {
return NGX_ERROR;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%s\" directive %s", name->data, rv);
return NGX_ERROR;
}
}
if (found) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%s\" directive is not allowed here", name->data);
return NGX_ERROR;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unknown directive \"%s\"", name->data);
return NGX_ERROR;
invalid:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of arguments in \"%s\" directive",
name->data);
return NGX_ERROR;
}
2. 代码分析
这段代码是 Nginx 配置文件(如 nginx.conf)解析器的关键部分。
每当 Nginx 读取到一个配置指令(directive)时(例如 worker_processes、server、listen 等),
就会调用此函数来:
- 查找该指令对应的处理函数(handler);
- 验证指令的使用上下文、参数数量、语法合法性;
- 调用模块注册的
set回调函数执行实际配置操作。
1. 函数签名
static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
返回值:NGX_OK 表示成功,NGX_ERROR 表示解析失败。
参数:cf:当前配置上下文(包含指令名、参数、模块信息等)。last:表示当前指令的“结束状态”:
- NGX_OK:普通指令以 ; 结尾;
- NGX_CONF_BLOCK_START:以 { 开始的块(如 http { ... });
- 其他值表示非法结束。
2. 局部变量
{
char *rv;
void *conf, **confp;
ngx_uint_t i, found;
ngx_str_t *name;
ngx_command_t *cmd;
rv:cmd->set函数的返回值conf:指向该指令应写入的配置结构体confp:二级指针,用于访问复杂配置结构i:模块遍历索引found:标记是否找到名称匹配的指令(即使上下文不合法也算“found”)name:当前要处理的指令名(cf->args->elts[0])cmd:当前遍历到的命令结构
3. 获取指令名
name = cf->args->elts;
4. 初始化 found 标志
found = 0;
初始化为 0,若在模块中找到同名指令,
即使上下文不对,也设为 1(用于区分“未知指令”和“指令位置错误”)。
5. 遍历所有已加载模块
for (i = 0; cf->cycle->modules[i]; i++) {
cf->cycle->modules 是 Nginx 启动时加载的所有模块的数组,以 NULL 结尾。
依次检查每个模块是否定义了命令。
1. 跳过无命令的模块
cmd = cf->cycle->modules[i]->commands;
if (cmd == NULL) {
continue;
}
每个模块通过 commands 字段注册自己支持的指令(ngx_command_t 数组)。
若为 NULL,说明该模块不提供任何配置指令,跳过。
2. 遍历该模块的所有指令
for ( /* void */ ; cmd->name.len; cmd++) {
ngx_command_t 数组以 name.len == 0 作为结束标志。
逐个检查指令名是否匹配。
1. 查找指令
if (name->len != cmd->name.len) {
continue;
}
指令名长度比较(快速过滤)
长度不同,肯定不匹配,跳过(避免不必要的字符串比较)
指令名字符串比较
if (ngx_strcmp(name->data, cmd->name.data) != 0) {
continue;
}
使用 ngx_strcmp(Nginx 的 strcmp 封装)比较指令名。
若不相等,跳过。
若相等,说明找到了同名指令。
found = 1;
标记已找到同名指令
即使后续因上下文不合法被跳过,也记录“此指令存在”,用于错误提示区分
2. 检查模块类型是否匹配当前上下文
if (cf->cycle->modules[i]->type != NGX_CONF_MODULE
&& cf->cycle->modules[i]->type != cf->module_type)
{
continue;
}
过滤掉在当前配置上下文中不该被处理的模块,
确保只有与当前配置阶段(或上下文)类型匹配的模块才能参与指令解析
cf->module_type 表示当前配置块所属的模块类型(如 NGX_HTTP_MODULE、NGX_CORE_MODULE)。
模块必须是 NGX_CONF_MODULE(配置模块)或与当前上下文类型匹配,否则跳过。
例如:在
http {}块中,cf->module_type == NGX_HTTP_MODULE,那么只有 HTTP 模块或 CONF 模块的指令才被接受。
NGX_CONF_MODULE:一个特殊的模块类型,
表示该模块是通用配置模块,
可以在任何上下文中被调用
3. 检查指令位置
/* is the directive's location right ? */
if (!(cmd->type & cf->cmd_type)) {
continue;
}
cmd->type 是指令允许出现的上下文掩码,如 NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF。cf->cmd_type 是当前配置块的上下文(如 NGX_HTTP_SRV_CONF)。
若 cmd->type 不包含当前上下文,说明该指令不能在此处使用,跳过。
例如:
listen指令的type包含NGX_HTTP_SRV_CONF,所以只能在server {}块中使用。
4. 检查非块指令是否以分号结束
if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"directive \"%s\" is not terminated by \";\"",
name->data);
return NGX_ERROR;
}
如果指令不是块指令(即不应有 {}),
但 last != NGX_OK(说明不是以 ; 结尾),报错。
5. 检查块指令是否以花括号开始
if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"directive \"%s\" has no opening \"{\"",
name->data);
return NGX_ERROR;
}
如果指令是块指令(如 http、server),
但 last != NGX_CONF_BLOCK_START(说明没有 {),报错。
6. 验证参数数量是否合法
/* is the directive's argument count right ? */
if (!(cmd->type & NGX_CONF_ANY)) {
如果指令不是 NGX_CONF_ANY(表示参数数量任意),则需严格检查参数个数。
处理布尔型指令(NGX_CONF_FLAG)
if (cmd->type & NGX_CONF_FLAG) {
if (cf->args->nelts != 2) {
goto invalid;
}
NGX_CONF_FLAG:指令必须有 1 个参数(加上指令名共 2 个词元),如 daemon on;。
参数数量 ≠ 2,跳转到 invalid 标签报错。
处理“至少1个参数”指令(NGX_CONF_1MORE)
} else if (cmd->type & NGX_CONF_1MORE) {
if (cf->args->nelts < 2) {
goto invalid;
}
至少 1 个参数(总词元 ≥ 2),
常用于可变参数指令。
} else if (cmd->type & NGX_CONF_2MORE) {
if (cf->args->nelts < 3) {
goto invalid;
}
处理“至少2个参数”指令(NGX_CONF_2MORE)
至少 2 个参数(总词元 ≥ 3)
检查参数总数是否超过最大限制
} else if (cf->args->nelts > NGX_CONF_MAX_ARGS) {
goto invalid;
Nginx 默认最多支持 8个参数。
超过则报错。
检查固定参数数量(通过位掩码)
} else if (!(cmd->type & argument_number[cf->args->nelts - 1]))
{
goto invalid;
}
}
argument_number[]是一个预定义数组,例如:static ngx_uint_t argument_number[] = { NGX_CONF_NOARGS, // 0 参数(仅指令名) NGX_CONF_TAKE1, // 1 参数 NGX_CONF_TAKE2, // 2 参数 NGX_CONF_TAKE3, NGX_CONF_TAKE4, NGX_CONF_TAKE5, NGX_CONF_TAKE6, NGX_CONF_TAKE7 };cf->args->nelts - 1是实际参数个数(去掉指令名)。- 检查
cmd->type是否包含对应的NGX_CONF_TAKEn位。 - 若不包含,说明参数数量不匹配,报错。
例如:
worker_processes是NGX_CONF_TAKE1,如果写成worker_processes 2 3;(2 个参数),就会失败。
7. 确定配置结构体的写入位置(conf)
/* set up the directive's configuration context */
conf = NULL;
初始化 conf 为 NULL,后续根据指令类型赋值。
情况1:NGX_DIRECT_CONF —— 直接使用模块上下文
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
用于核心模块(如 NGX_CORE_MODULE),配置直接存在 cf->ctx 数组中。cf->ctx 是一个 void**,每个模块占一个 slot。
直接取第 index 个元素作为配置结构体指针。
情况2:NGX_MAIN_CONF —— 主配置(取地址)
} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);
注意这里是 &(...),取的是指针的地址。
用于需要修改指针本身的情况(如动态分配配置结构)。
情况3:普通配置
} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[cf->cycle->modules[i]->ctx_index];
}
}
cf->ctx指向当前配置上下文(如ngx_http_conf_ctx_t)。cmd->conf是一个偏移量(offsetof),指向该模块在上下文中的配置指针数组。confp是一个void*[],存储 main/srv/loc 三级配置。ctx_index是该模块在其类型(如 HTTP 模块)中的索引。- 最终
conf指向具体的配置结构体(如ngx_http_core_srv_conf_t)。
对于HTTP模块,
cf->ctx所指向的结构体中有 3 个字段(3 个指向数组的指针),cmd->conf是某个字段相对于这个结构体的偏移量,cf->ctx + cmd->conf指向结构体其中一个字段(一个指针,指向一个指针数组)confpctx_index是该模块在confp数组中的索引
所得conf指向具体的配置结构体
这是 Nginx 多级配置继承机制的核心。
8. 调用指令的 set 函数(真正处理配置)
rv = cmd->set(cf, cmd, conf);
cmd->set是模块注册的回调函数,负责解析参数并写入conf。- 返回值:
NGX_CONF_OK:成功;NGX_CONF_ERROR:失败;- 其他:错误字符串(如
"invalid value")。
9. 处理 set 函数的返回值
if (rv == NGX_CONF_OK) {
return NGX_OK;
}
if (rv == NGX_CONF_ERROR) {
return NGX_ERROR;
}
- 成功则返回
NGX_OK; - 明确错误返回
NGX_ERROR;
处理自定义错误信息
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%s\" directive %s", name->data, rv);
return NGX_ERROR;
}
}
如果 rv 是字符串(如 "port is invalid"),
打印 "listen directive port is invalid"。
返回错误。
6. 指令名存在,但上下文不合法
if (found) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%s\" directive is not allowed here", name->data);
return NGX_ERROR;
}
- 说明指令名存在,但在当前配置块中不允许使用。
7. 完全未知的指令
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unknown directive \"%s\"", name->data);
return NGX_ERROR;
没有任何模块注册该指令名,属于拼写错误或无效指令
8. 参数数量错误的统一处理(invalid 标签)
invalid:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of arguments in \"%s\" directive",
name->data);
return NGX_ERROR;
}
跳转至此处,说明参数数量不符合要求。
9. 总结:
ngx_conf_handler 的核心作用
- 指令路由:根据指令名找到对应的模块和
ngx_command_t。 - 上下文校验:确保指令用在允许的配置块中(如
http、server)。 - 语法校验:
- 块指令必须有
{; - 普通指令必须有
;; - 参数数量必须合法。
- 块指令必须有
- 配置定位:确定该指令的值应写入哪个配置结构体(main/srv/loc)。
- 调用处理函数:执行模块注册的
set函数完成实际配置。 - 错误反馈:提供清晰的错误信息(未知指令、位置错误、参数错误等)。
这是 Nginx 高度模块化、可扩展配置系统的核心实现,也是其配置语法强大而严谨的基础。
3. ngx_conf_handler 函数逻辑流程
一、函数入口与初始化
1.1 输入参数
ngx_conf_t *cf:当前配置上下文- 包含:当前指令参数(
cf->args)、模块类型(cf->module_type)、配置上下文(cf->ctx)、命令类型(cf->cmd_type)等
- 包含:当前指令参数(
ngx_int_t last:当前指令的结束状态NGX_OK→ 以;结尾(普通指令)NGX_CONF_BLOCK_START→ 以{开始(块指令)- 其他值 → 语法错误
1.2 局部变量初始化
name = cf->args->elts:取指令名(args[0])found = 0:标记是否找到同名指令(用于区分“未知指令” vs “位置错误”)
二、遍历所有已加载模块
2.1 外层循环:for (i = 0; cf->cycle->modules[i]; i++)
- 遍历
cf->cycle->modules[]数组(以NULL结尾)
2.2 跳过无指令的模块
- 若
modules[i]->commands == NULL,continue
三、遍历当前模块的所有指令(ngx_command_t 数组)
3.1 内层循环:for (; cmd->name.len; cmd++)
- 以
cmd->name.len == 0作为终止条件
3.2 指令名匹配检查
3.2.1 长度快速过滤
- 若
name->len != cmd->name.len→continue
3.2.2 字符串精确匹配
- 若
ngx_strcmp(name->data, cmd->name.data) != 0→continue
3.2.3 匹配成功 → 标记 found = 1
- 表示“该指令名存在”,即使后续因上下文被拒,也记录此状态
四、模块类型合法性检查
4.1 条件判断
if (module->type != NGX_CONF_MODULE && module->type != cf->module_type)
4.2 逻辑含义
- 仅以下两类模块可参与处理:
- 通用配置模块:
type == NGX_CONF_MODULE - 当前上下文匹配模块:
type == cf->module_type- 例如:在
http {}块中,cf->module_type == NGX_HTTP_MODULE
- 例如:在
- 通用配置模块:
4.3 不匹配 → continue(跳过该指令)
五、指令上下文(作用域)合法性检查
5.1 位掩码匹配
- 检查:
(cmd->type & cf->cmd_type) != 0cmd->type:指令允许出现的上下文(如NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF)cf->cmd_type:当前配置块的上下文类型(如NGX_HTTP_SRV_CONF)
5.2 不匹配 → continue(指令不允许在此处使用)
六、指令语法合法性检查
6.1 块指令 vs 普通指令结束符检查
6.1.1 普通指令(非 NGX_CONF_BLOCK)
- 要求:
last == NGX_OK(即以;结尾) - 否则报错:
"directive \"X\" is not terminated by \";\""
6.1.2 块指令(cmd->type & NGX_CONF_BLOCK)
- 要求:
last == NGX_CONF_BLOCK_START(即后跟{) - 否则报错:
"directive \"X\" has no opening \"{\""
6.2 参数数量合法性检查(若非 NGX_CONF_ANY)
6.2.1 NGX_CONF_FLAG
- 要求:
cf->args->nelts == 2(指令名 + 1 个参数,如on/off)
6.2.2 NGX_CONF_1MORE
- 要求:
cf->args->nelts >= 2(至少 1 个参数)
6.2.3 NGX_CONF_2MORE
- 要求:
cf->args->nelts >= 3(至少 2 个参数)
6.2.4 固定参数数量(NGX_CONF_TAKE1 ~ TAKE7)
- 通过
argument_number[cf->args->nelts - 1]位掩码匹配 - 例如:
TAKE2要求nelts == 3
6.2.5 参数总数上限
- 若
cf->args->nelts > NGX_CONF_MAX_ARGS(通常为 5)→ 报错
6.2.6 任意参数(NGX_CONF_ANY)
- 跳过所有参数检查(极少见)
6.3 参数错误统一处理
- 跳转到
invalid:标签 - 报错:
"invalid number of arguments in \"X\" directive"
七、确定配置写入位置(conf 指针定位)
7.1 NGX_DIRECT_CONF 类型
conf = ((void **)cf->ctx)[module->index]- 用于核心模块,直接使用
cf->ctx中的配置结构体
7.2 NGX_MAIN_CONF 类型
conf = &(((void **)cf->ctx)[module->index])- 取配置指针的地址(较少使用)
7.3 普通模块(HTTP/Stream 等)
- 通过偏移量
cmd->conf定位到配置指针数组 confp = *(void **)((char *)cf->ctx + cmd->conf)conf = confp[module->ctx_index]- 支持 main/srv/loc 三级配置继承
八、调用指令处理函数 cmd->set
8.1 调用
rv = cmd->set(cf, cmd, conf)
8.2 处理返回值
8.2.1 rv == NGX_CONF_OK
- 成功 →
return NGX_OK
8.2.2 rv == NGX_CONF_ERROR
- 失败 →
return NGX_ERROR
8.2.3 rv == char*(错误字符串)
- 报错:
"\"X\" directive Y"(Y 为错误描述) return NGX_ERROR
九、遍历结束后的兜底错误处理
9.1 所有模块遍历完毕,未找到合法处理路径
9.2 判断错误类型
9.2.1 found == 1
- 指令名存在,但上下文/模块类型/作用域不匹配
- 报错:
"\"X\" directive is not allowed here"
9.2.2 found == 0
- 完全未知的指令
- 报错:
"unknown directive \"X\""
9.3 统一返回 NGX_ERROR
十、函数出口
- 唯一成功路径:
cmd->set返回NGX_CONF_OK→return NGX_OK - 所有其他路径 →
return NGX_ERROR
总结:四重过滤 + 一层执行
- 名称过滤 → 是我认识的指令吗?
- 模块过滤 → 当前上下文允许这个模块参与吗?
- 作用域过滤 → 这个指令能在这里用吗?
- 语法过滤 → 参数、分号、花括号对吗?
- 执行层 → 调用
set函数真正配置
这种层层递进、严格校验的设计,使 Nginx 配置系统兼具 灵活性、安全性与可扩展性。
ngx_conf_handler(cf, last)
│
├── 📥 输入参数
│ ├── cf: ngx_conf_t*
│ │ ├── cf->args → 当前指令词元数组(含指令名 + 参数)
│ │ ├── cf->module_type → 当前模块上下文类型(如 NGX_HTTP_MODULE)
│ │ ├── cf->cmd_type → 当前配置块允许的指令类型位掩码
│ │ └── cf->ctx → 配置上下文指针(如 http_conf_ctx)
│ └── last: ngx_int_t
│ ├── NGX_OK → 指令以 ";" 结尾
│ ├── NGX_CONF_BLOCK_START → 指令后跟 "{"
│ └── 其他值 → 语法错误
│
├── 🧹 初始化
│ ├── name = cf->args[0] → 提取指令名(ngx_str_t*)
│ └── found = 0 → 标记是否找到同名指令
│
├── 🔁 遍历所有模块 (for i in modules)
│ ├── 跳过 commands == NULL 的模块
│ │
│ └── 🔁 遍历模块的每条指令 (for cmd in module->commands)
│ ├── 🔍 指令名匹配?
│ │ ├── 长度不同? → continue
│ │ ├── 字符串不同? → continue
│ │ └── ✅ 匹配 → found = 1
│ │
│ ├── 🧩 模块类型合法?
│ │ └── if (type != NGX_CONF_MODULE && type != cf->module_type)
│ │ └── ❌ 不合法 → continue
│ │
│ ├── 📍 指令上下文合法?(作用域检查)
│ │ └── if (!(cmd->type & cf->cmd_type))
│ │ └── ❌ 不允许在此处使用 → continue
│ │
│ ├── 🔤 语法合法性检查
│ │ ├── 🧱 块指令 vs 普通指令
│ │ │ ├── 非块指令 + last != NGX_OK → ❌ 缺分号
│ │ │ └── 块指令 + last != NGX_CONF_BLOCK_START → ❌ 缺 "{"
│ │ │
│ │ └── 📏 参数数量检查(若非 NGX_CONF_ANY)
│ │ ├── NGX_CONF_FLAG → 必须 2 个词元(指令+1参)
│ │ ├── NGX_CONF_1MORE → ≥2 词元
│ │ ├── NGX_CONF_2MORE → ≥3 词元
│ │ ├── NGX_CONF_TAKE1~5 → 严格匹配(通过 argument_number[] 位掩码)
│ │ ├── 超过 NGX_CONF_MAX_ARGS(通常5)→ ❌
│ │ └── 不匹配 → goto invalid
│ │
│ ├── 🎯 定位配置结构体 (conf)
│ │ ├── NGX_DIRECT_CONF → conf = cf->ctx[module->index]
│ │ ├── NGX_MAIN_CONF → conf = &cf->ctx[module->index]
│ │ └── 普通模块(HTTP/Stream)
│ │ └── conf = ((cf->ctx + cmd->conf))[module->ctx_index]
│ │
│ └── ⚙️ 调用 cmd->set(cf, cmd, conf)
│ ├── 返回 NGX_CONF_OK → ✅ return NGX_OK
│ ├── 返回 NGX_CONF_ERROR → ❌ return NGX_ERROR
│ └── 返回 char*(错误串)→ log + ❌ return NGX_ERROR
│
├── 🚨 遍历结束,未成功处理
│ ├── if (found == 1)
│ │ └── 💥 "directive 'X' is not allowed here"
│ └── else
│ └── ❓ "unknown directive 'X'"
│
└── 🛑 统一返回 NGX_ERROR(除成功路径外)
更多推荐
所有评论(0)