定义在 src\http\ngx_http_core_module.c
static char *
ngx_http_core_error_page(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{ngx_http_core_loc_conf_t *clcf = conf;u_char *p;ngx_int_t overwrite;ngx_str_t *value, uri, args;ngx_uint_t i, n;ngx_http_err_page_t *err;ngx_http_complex_value_t cv;ngx_http_compile_complex_value_t ccv;if (clcf->error_pages == NULL) {clcf->error_pages = ngx_array_create(cf->pool, 4,sizeof(ngx_http_err_page_t));if (clcf->error_pages == NULL) {return NGX_CONF_ERROR;}}value = cf->args->elts;i = cf->args->nelts - 2;if (value[i].data[0] == '=') {if (i == 1) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid value \"%V\"", &value[i]);return NGX_CONF_ERROR;}if (value[i].len > 1) {overwrite = ngx_atoi(&value[i].data[1], value[i].len - 1);if (overwrite == NGX_ERROR) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid value \"%V\"", &value[i]);return NGX_CONF_ERROR;}} else {overwrite = 0;}n = 2;} else {overwrite = -1;n = 1;}uri = value[cf->args->nelts - 1];ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));ccv.cf = cf;ccv.value = &uri;ccv.complex_value = &cv;if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {return NGX_CONF_ERROR;}ngx_str_null(&args);if (cv.lengths == NULL && uri.len && uri.data[0] == '/') {p = (u_char *) ngx_strchr(uri.data, '?');if (p) {cv.value.len = p - uri.data;cv.value.data = uri.data;p++;args.len = (uri.data + uri.len) - p;args.data = p;}}for (i = 1; i < cf->args->nelts - n; i++) {err = ngx_array_push(clcf->error_pages);if (err == NULL) {return NGX_CONF_ERROR;}err->status = ngx_atoi(value[i].data, value[i].len);if (err->status == NGX_ERROR || err->status == 499) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid value \"%V\"", &value[i]);return NGX_CONF_ERROR;}if (err->status < 300 || err->status > 599) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"value \"%V\" must be between 300 and 599",&value[i]);return NGX_CONF_ERROR;}err->overwrite = overwrite;if (overwrite == -1) {switch (err->status) {case NGX_HTTP_TO_HTTPS:case NGX_HTTPS_CERT_ERROR:case NGX_HTTPS_NO_CERT:case NGX_HTTP_REQUEST_HEADER_TOO_LARGE:err->overwrite = NGX_HTTP_BAD_REQUEST;}}err->value = cv;err->args = args;}return NGX_CONF_OK;
}
ngx_http_core_error_page 函数是 Nginx 中用于解析和配置自定义错误页面的核心函数
if (clcf->error_pages == NULL) {clcf->error_pages = ngx_array_create(cf->pool, 4,sizeof(ngx_http_err_page_t));if (clcf->error_pages == NULL) {return NGX_CONF_ERROR;}}
检查当前配置块(
clcf)的error_pages数组是否已初始化如果
error_pages未初始化(NULL),说明这是第一次为此配置块设置错误页面规则,需要创建数组ngx_http_core_loc_conf_t-CSDN博客
clcf->error_pages是 Nginx 中ngx_http_core_loc_conf_t结构体的一个字段,用于存储当前配置块中定义的所有error_page规则ngx_http_err_page_t-CSDN博客
每个元素是一个
ngx_http_err_page_t结构体,记录了以下信息:
- 错误状态码 (如
404、500)- 重写后的响应状态码
- 目标 URI
- URI 的查询参数
value = cf->args->elts;i = cf->args->nelts - 2;if (value[i].data[0] == '=') {if (i == 1) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid value \"%V\"", &value[i]);return NGX_CONF_ERROR;}if (value[i].len > 1) {overwrite = ngx_atoi(&value[i].data[1], value[i].len - 1);if (overwrite == NGX_ERROR) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid value \"%V\"", &value[i]);return NGX_CONF_ERROR;}} else {overwrite = 0;}n = 2;} else {overwrite = -1;n = 1;}
value = cf->args->elts;获取当前配置指令的所有参数列表
i = cf->args->nelts - 2;
cf->args->nelts是参数总数
i的值为参数总数 - 2,即最后一个参数前的参数索引此时 i=4
if (value[i].data[0] == '=') {检查当前参数是否以
=开头(即是否为覆盖状态码的语法)
- 若参数以
=开头(如=200),则进入覆盖状态码的解析流程。- 否则,跳转到
else分支,表示不覆盖状态码此时条件不成立
else { overwrite = -1; n = 1; }处理不覆盖状态码的情况
overwrite = -1:表示不覆盖原始错误码
n = 1:仅需跳过uri参数,后续处理所有错误码参数
uri = value[cf->args->nelts - 1];ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));ccv.cf = cf;ccv.value = &uri;ccv.complex_value = &cv;
uri = value[cf->args->nelts - 1];提取
error_page指令的最后一个参数(即目标 URI)
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));初始化
ngx_http_compile_complex_value_t结构体ngx_http_compile_complex_value_t-CSDN博客
ccv.cf = cf;将当前配置解析上下文(
cf)关联到编译器结构体
ccv是编译复杂值的上下文结构体。cf包含配置解析的内存池、日志等关键信息
ccv.value = &uri;
uri是用户配置的字符串(如"/404.html")ccv.value是编译器的输入参数。
ccv.complex_value = &cv;
cv是ngx_http_complex_value_t类型的变量,用于存储编译后的动态值。ccv.complex_value是编译器的输出目标。
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {return NGX_CONF_ERROR;}
调用
ngx_http_compile_complex_value函数,并检查返回值
error_page的 URI 可能包含变量(如/error_$status.html),需要通过ngx_http_compile_complex_value函数将其编译为运行时可解析的结构体。若编译失败,需终止配置解析ngx_http_compile_complex_value-CSDN博客
ngx_str_null(&args);
将
args字符串(ngx_str_t类型)初始化为空字符串
if (cv.lengths == NULL && uri.len && uri.data[0] == '/') {p = (u_char *) ngx_strchr(uri.data, '?');if (p) {cv.value.len = p - uri.data;cv.value.data = uri.data;p++;args.len = (uri.data + uri.len) - p;args.data = p;}}
if (cv.lengths == NULL && uri.len && uri.data[0] == '/') {检查 URI 是否为静态路径(无变量)且以
/开头
cv.lengths == NULL:表示 URI 不含变量(如$variable),是纯静态字符串。uri.len:确保 URI 非空。uri.data[0] == '/':URI 必须以/开头(本地路径)。此时 条件成立
p = (u_char *) ngx_strchr(uri.data, '?');在 URI 中查找
?的位置,用于分割路径和查询参数此时 p=NULL
for (i = 1; i < cf->args->nelts - n; i++) {err = ngx_array_push(clcf->error_pages);if (err == NULL) {return NGX_CONF_ERROR;}
for (i = 1; i < cf->args->nelts - n; i++) {遍历用户配置的错误码参数
i = 1:从第二个参数开始(第一个参数是error_page指令本身)
cf->args->nelts:配置指令的总参数个数
n:表示后续参数占用的槽位数循环条件 :
i < 总参数数 - n,即处理所有状态码参数,跳过覆盖码和 URI 参数。
- 示例 :
- 配置
error_page 404 500 =200 /error.html;
cf->args->nelts = 5(参数列表:error_page,404,500,=200,/error.html)。n = 2(覆盖码=200和 URI/error.html占用 2 个槽位)。- 循环次数:
i < 5 - 2→i < 3→i=1(处理404)、i=2(处理500)。
err = ngx_array_push(clcf->error_pages);为当前错误码分配一个
ngx_http_err_page_t结构,并添加到clcf->error_pages数组ngx_http_err_page_t-CSDN博客
err->status = ngx_atoi(value[i].data, value[i].len);if (err->status == NGX_ERROR || err->status == 499) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid value \"%V\"", &value[i]);return NGX_CONF_ERROR;}if (err->status < 300 || err->status > 599) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"value \"%V\" must be between 300 and 599",&value[i]);return NGX_CONF_ERROR;}
err->status = ngx_atoi(value[i].data, value[i].len);将用户配置的字符串形式的状态码(如
"404")转换为整数if (err->status == NGX_ERROR || err->status == 499) {验证状态码的合法性
NGX_ERROR:表示字符串转换失败
499:Nginx 内部使用的特殊状态码(客户端主动关闭连接),禁止用户配置if (err->status < 300 || err->status > 599) {确保状态码在 HTTP 标准的错误码范围内。
HTTP 状态码规则:
3xx:重定向。
4xx:客户端错误。
5xx:服务器端错误。Nginx 要求
error_page仅处理 300-599 的状态码。禁止配置非错误码(如
200 OK)或无效范围值。
err->overwrite = overwrite;if (overwrite == -1) {switch (err->status) {case NGX_HTTP_TO_HTTPS:case NGX_HTTPS_CERT_ERROR:case NGX_HTTPS_NO_CERT:case NGX_HTTP_REQUEST_HEADER_TOO_LARGE:err->overwrite = NGX_HTTP_BAD_REQUEST;}}err->value = cv;err->args = args;
err->overwrite = overwrite;将用户配置的覆盖码(或默认值
-1)赋值给错误页结构体if (overwrite == -1) {当用户未显式指定覆盖码时,检查是否需要为某些特殊状态码设置默认覆盖值
switch (err->status) {case NGX_HTTP_TO_HTTPS:case NGX_HTTPS_CERT_ERROR:case NGX_HTTPS_NO_CERT:case NGX_HTTP_REQUEST_HEADER_TOO_LARGE:err->overwrite = NGX_HTTP_BAD_REQUEST; }为特定状态码设置默认覆盖码
400 Bad Request。匹配的状态码 :
NGX_HTTP_TO_HTTPS(497):客户端通过 HTTP 请求 HTTPS 服务。
NGX_HTTPS_CERT_ERROR(495):客户端证书验证失败。
NGX_HTTPS_NO_CERT(496):需要客户端证书但未提供。
NGX_HTTP_REQUEST_HEADER_TOO_LARGE(494):请求头过大。覆盖逻辑 :将响应码改为
400 Bad Request,隐藏内部实现细节。意义 :
安全性 :避免暴露敏感状态码(如 495、496)给客户端。
标准化 :统一返回标准 HTTP 状态码,符合 RFC 规范。
err->value = cv;将处理后的 URI 存储到错误页结构体
cv是ngx_http_complex_value_t类型,包含静态或动态 URI(如/404.html或包含变量的/error_$status.html)。在错误发生时,Nginx 会根据
cv的值执行内部跳转或返回静态文件。err->args = args;将查询参数保存到错误页结构体。
args是从 URI 中解析出的查询参数(如code=404)。在重定向或内部跳转时,携带参数传递给目标资源。
return NGX_CONF_OK;
返回 NGX_CONF_OK;
