PAYATHON 2026

如何在 CodeIgniter 中处理 4xx 和 5xx 错误?

数据小陈

结论

CodeIgniter 没有类似 show_404() 的统一函数来自动识别和处理所有 HTTP 错误。通常需要按错误来源分别处理:

  1. 应用程序主动产生错误时,明确设置 HTTP 状态码,再加载对应视图。
  2. 将重复的处理逻辑封装成统一的错误响应方法。
  3. 请求尚未进入 CodeIgniter 就已出错时,例如出现 502、504 或 URI 过长,应在 Nginx、Apache、负载均衡器等上游组件中配置错误页面。
  4. 不要只显示错误模板却仍返回 200 OK,否则浏览器、搜索引擎和 API 客户端会把请求误判为成功。

show_404() 只是 CodeIgniter 为”页面不存在”提供的快捷方式,并不意味着其他状态码无法处理。

CodeIgniter 3 的基本处理方式

在 CodeIgniter 3 中,可以通过 Output 类设置状态码,再加载对应视图:

public function forbidden()
{
    $this->output->set_status_header(403);

    $this->load->view('errors/403', [
        'message' => '你没有访问该资源的权限。'
    ]);
}

对应的视图可以放在:

application/views/errors/403.php

例如:

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>403 Forbidden</title>
</head>
<body>
    <h1>无权访问</h1>
    <p><?= html_escape($message) ?></p>
</body>
</html>

其他状态码也可以用同样的方法处理:

$this->output->set_status_header(400);
$this->load->view('errors/400');
$this->output->set_status_header(405);
$this->output->set_header('Allow: GET, POST');
$this->load->view('errors/405');
$this->output->set_status_header(503);
$this->output->set_header('Retry-After: 3600');
$this->load->view('errors/503');

封装统一的错误响应方法

如果应用需要处理多种错误,没有必要在每个控制器中重复编写相同代码。可以把这部分逻辑封装到自定义基础控制器中:

class MY_Controller extends CI_Controller
{
    protected function renderHttpError($statusCode, $message = '')
    {
        $supportedCodes = [
            400, 401, 402, 403, 405, 406, 407, 408,
            409, 410, 411, 412, 413, 414, 415,
            500, 501, 502, 503, 504, 505
        ];

        if (!in_array($statusCode, $supportedCodes, true)) {
            $statusCode = 500;
        }

        $view = 'errors/' . $statusCode;
        $viewFile = APPPATH . 'views/' . $view . '.php';

        if (!is_file($viewFile)) {
            $view = 'errors/default';
        }

        $this->output->set_status_header($statusCode);

        $this->load->view($view, [
            'statusCode' => $statusCode,
            'message'    => $message
        ]);
    }
}

业务控制器继承 MY_Controller 后,就可以直接调用这个方法:

class Orders extends MY_Controller
{
    public function detail($id)
    {
        if (!$this->session->userdata('logged_in')) {
            return $this->renderHttpError(401, '请先登录。');
        }

        if (!$this->userCanViewOrder($id)) {
            return $this->renderHttpError(403, '你不能查看该订单。');
        }

        $order = $this->order_model->find($id);

        if ($order === null) {
            show_404();
            return;
        }

        $this->load->view('orders/detail', [
            'order' => $order
        ]);
    }
}

如果不想维护大量内容相近的模板,也可以只保留一个通用模板,根据 $statusCode 显示不同文案。

使用 show_error()

部分 CodeIgniter 版本提供了 show_error(),用于显示通用错误页面并设置状态码。不过,不同主要版本的参数顺序并不完全一致,直接照搬其他版本的调用方式可能会出错。

CodeIgniter 3 中常见的调用形式是:

show_error(
    '你没有访问该资源的权限。',
    403,
    'Forbidden'
);

这种方式适合简单的错误页面。如果每个状态码都需要不同的页面布局,明确设置状态码并加载对应视图会更清楚。

使用前应检查项目当前安装版本的函数定义:

system/core/Common.php

CodeIgniter 4 的处理方式

CodeIgniter 4 可以通过 Response 对象设置状态码并返回视图:

public function update()
{
    if (!$this->request->is('post')) {
        return $this->response
            ->setStatusCode(405)
            ->setHeader('Allow', 'POST')
            ->setBody(view('errors/405'));
    }

    // 正常业务逻辑
}

也可以将它封装成控制器方法:

protected function errorResponse(int $statusCode, string $message = '')
{
    $viewPath = 'errors/' . $statusCode;

    return $this->response
        ->setStatusCode($statusCode)
        ->setBody(view($viewPath, [
            'statusCode' => $statusCode,
            'message'    => $message,
        ]));
}

调用方式如下:

return $this->errorResponse(403, '你没有执行此操作的权限。');

如果页面不存在,CodeIgniter 4 通常使用:

throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();

哪些错误应该由应用程序处理

请求已经进入 CodeIgniter,而且应用能够判断失败原因时,才适合在控制器中处理。例如:

  • 400 Bad Request:请求参数或数据格式不合法。
  • 401 Unauthorized:用户尚未通过身份认证。
  • 403 Forbidden:身份已确认,但没有权限。
  • 404 Not Found:资源不存在。
  • 405 Method Not Allowed:当前接口不允许使用该请求方法。
  • 409 Conflict:资源状态冲突,例如重复创建或版本冲突。
  • 410 Gone:资源已被永久删除。
  • 413 Content Too Large:业务层判断上传内容过大。
  • 415 Unsupported Media Type:不支持请求的 Content-Type。
  • 500 Internal Server Error:应用内部发生未预期的错误。
  • 501 Not Implemented:当前功能或请求能力尚未实现。
  • 503 Service Unavailable:应用正在维护或暂时不可用。

401 的标准原因短语是 Unauthorized,实际含义更接近”尚未认证”,而不是”已经认证但没有权限”。用户已完成身份认证但权限不足时,一般应返回 403。

哪些错误可能根本不会进入 CodeIgniter

有些错误无法在控制器中捕获。下面这些错误经常由 Web 服务器、代理或网关在执行 PHP 之前产生:

  • 400:HTTP 请求本身无法解析。
  • 408:连接或请求读取超时。
  • 411:服务器要求提供 Content-Length。
  • 413:请求体超过 Nginx、Apache 或 PHP 设置的限制。
  • 414:URI 超过服务器允许的长度。
  • 431:请求头过大。
  • 502:上游 PHP-FPM 或其他服务返回无效响应。
  • 504:反向代理等待上游服务超时。
  • 505:服务器不支持请求所使用的 HTTP 版本。

发生这些错误时,CodeIgniter 尚未运行,无法加载框架中的视图,需要改为在 Nginx 或 Apache 中配置静态错误页面。

Nginx 示例:

error_page 400 /errors/400.html;
error_page 403 /errors/403.html;
error_page 404 /errors/404.html;
error_page 500 502 503 504 /errors/50x.html;

location /errors/ {
    root /var/www/example/public;
    internal;
}

Apache 示例:

ErrorDocument 400 /errors/400.html
ErrorDocument 403 /errors/403.html
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html
ErrorDocument 503 /errors/503.html

502 和 504 最好使用静态页面。PHP-FPM 或应用服务已经不可用时,再将错误请求转发给 CodeIgniter,通常仍无法生成页面。

认证错误需要附加响应头

有些状态码除了需要设置状态码,还必须或应该返回相应的 HTTP 头。

使用 HTTP Basic Authentication 时,401 响应应包含 WWW-Authenticate:

$this->output->set_status_header(401);
$this->output->set_header('WWW-Authenticate: Basic realm="Restricted Area"');
$this->load->view('errors/401');

返回 405 时,应说明允许使用哪些方法:

$this->output->set_status_header(405);
$this->output->set_header('Allow: GET, POST');
$this->load->view('errors/405');

服务暂时不可用时,503 可以提示客户端稍后重试:

$this->output->set_status_header(503);
$this->output->set_header('Retry-After: 3600');
$this->load->view('errors/503');

处理 500 错误时的注意事项

业务代码可以主动返回 500,但语法错误、内存耗尽、数据库驱动异常和部分未捕获异常可能会进入 CodeIgniter 自身的异常处理流程,而不是由控制器方法处理。

生产环境的错误页应做到两点:

  • 向用户显示简洁、易懂的提示。
  • 将详细异常、堆栈和请求信息写入日志,不要直接显示在页面中。

数据库账号、SQL 语句、文件绝对路径、堆栈信息和环境变量尤其不应暴露给用户。

建议的职责划分

一种较稳妥的分工方式是:

  • CodeIgniter 处理业务能够识别的 4xx 和应用主动返回的 5xx。
  • 自定义基础控制器或服务负责统一选择错误模板。
  • Nginx、Apache、CDN 或负载均衡器处理请求进入应用之前产生的错误。
  • 404 可以继续使用 show_404()。
  • 401、403、405、409、415 等状态由业务代码明确判断。
  • 500 的详细信息写入日志,页面只向用户显示通用提示。
  • 502、504 等网关错误使用独立的静态页面。

这样可以为不同错误显示不同模板,同时保证实际返回的 HTTP 状态码准确无误。

备注:内容仅供参考。