如何在 CodeIgniter 中处理 4xx 和 5xx 错误?
结论
CodeIgniter 没有类似 show_404() 的统一函数来自动识别和处理所有 HTTP 错误。通常需要按错误来源分别处理:
- 应用程序主动产生错误时,明确设置 HTTP 状态码,再加载对应视图。
- 将重复的处理逻辑封装成统一的错误响应方法。
- 请求尚未进入 CodeIgniter 就已出错时,例如出现
502、504或 URI 过长,应在 Nginx、Apache、负载均衡器等上游组件中配置错误页面。 - 不要只显示错误模板却仍返回
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 状态码准确无误。
备注:内容仅供参考。
