ngx_http_core_error_page

定义在 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 结构体,记录了以下信息:

  • 错误状态码 (如 404500
  • 重写后的响应状态码
  • 目标 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;
  • cvngx_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 - 2i < 3i=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 存储到错误页结构体

cvngx_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;


相关推荐
Cloud_.2 小时前
用Nginx实现负载均衡与高可用架构(整合Keepalived)
nginx·架构·负载均衡·keepalived
Shi_haoliu2 小时前
各种网址整理-vue,前端,linux,ai前端开发,各种开发能用到的网址和一些有用的博客
linux·前端·javascript·vue.js·nginx·前端框架·pdf
LYX36938 小时前
nginx https配置
运维·nginx
java搬砖工-苤-初心不变9 小时前
关于 Nginx 配置中 proxy_set_header Host $host 的作用及其对 HTTP 请求头影响的详细说明,结合示例展示设置前后的差异
网络·nginx·http
裁二尺秋风10 小时前
Nginx — Nginx处理Web请求机制解析
前端·nginx
Kendra9191 天前
Keepalive+LVS+Nginx+NFS高可用架构
nginx·架构·lvs
若云止水1 天前
ngx_http_core_main_conf_t
nginx
开发小能手-roy1 天前
Ubuntu 系统中安装 Nginx
数据库·nginx·ubuntu
小破程序员1 天前
docker安装ngnix
服务器·nginx·docker
棕生1 天前
架构师面试(二十三):负载均衡
nginx·负载均衡·lvs·架构师面试·rpc连接池·vip+keepalive