自定义错误页面
OxPHP 为 4xx 和 5xx 响应提供带有品牌样式的 HTML 错误页面。每个页面在启动时从磁盘读取一次,随后从内存中提供,因此在请求处理期间不会发生任何磁盘 I/O。
工作原理
- 启动时加载。 在启动时,OxPHP 会读取
ERROR_PAGES_DIR指定的目录,并将每个有效的{status}.html文件加载到内存中。 - 命名规则。 文件名必须是 400–599 范围内的数字 HTTP 状态码(例如
404.html、503.html)。名称为非数字、状态码超出该范围(包括200.html)或扩展名非.html的文件会被静默忽略。 - 替换响应体。 当 OxPHP 生成 4xx 或 5xx 响应时,它会检查是否存在匹配的预加载错误页面。如果存在,OxPHP 只替换响应体以及描述响应体的那些头部:
Content-Type被设为text/html; charset=utf-8,Content-Length被设为页面大小,而与原始响应体绑定的头部(Content-Encoding、ETag、Last-Modified)会被丢弃,以免它们对替换后的内容做出错误的标注或触发重新验证(例如,被ob_gzhandler压缩过的 PHP 错误响应体,不会让 HTML 页面残留Content-Encoding: gzip的标记)。那些描述响应语义而非响应体的头部则会被保留到自定义页面中——416 Range Not Satisfiable上的Content-Range、529 Site is overloaded上的Retry-After,以及405 Method Not Allowed上的Allow。 - 回退。 如果目录在启动时不存在或无法读取,OxPHP 会记录一条警告并在没有自定义错误页面的情况下继续运行。错误响应会回退为纯文本响应体,直到目录问题修复且服务器重启为止。
配置
| 变量 | 默认值 | 说明 |
|---|---|---|
ERROR_PAGES_DIR |
(未设置) | 包含自定义错误页面 HTML 文件的目录。文件名必须为对应 400–599 状态码的 {status}.html。未设置时,错误响应使用纯文本响应体 |
示例页面
每个错误页面都是一个名为 {status}.html 的自包含 HTML 文件。请让它们使用内联样式,不引用任何外部资源——否则一个失败的二次请求本身就会破坏错误页面。
可复用模板
将下面的内容复制到每个 {status}.html 中,并修改 <title>、<h1> 和 <p>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>500 — Internal Server Error</title>
<style>
body { font-family: system-ui, sans-serif; text-align: center; padding: 4rem 1rem; color: #333; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
p { color: #666; }
</style>
</head>
<body>
<h1>Something went wrong</h1>
<p>Please try again in a moment.</p>
</body>
</html>值得提供的状态码
OxPHP 会替换每一个进入响应流水线的 4xx 或 5xx 响应的响应体。以下是它自身会返回的状态码,因此建议为每一个都提供一个文件:
| 文件 | 状态码 | OxPHP 何时返回它 |
|---|---|---|
400.html |
Bad Request | 一个未携带 Content-Type 头部的 QUERY 请求(RFC 10008) |
404.html |
Not Found | 没有匹配的文件或路由;被屏蔽的 dotfile(.env、.git/);Framework 模式下的直接 .php 请求;默认的 PHP_DENY_PATHS 回退 |
413.html |
Payload Too Large | 请求体超过最大大小 |
416.html |
Range Not Satisfiable | 静态文件上一个无法满足的 Range 头部(Content-Range 会被保留) |
500.html |
Internal Server Error | 未捕获的或致命的 PHP 错误 |
503.html |
Service Unavailable | 关闭期间的优雅排空 |
504.html |
Gateway Timeout | 请求超过了 REQUEST_TIMEOUT_SECONDS |
529.html |
Site is overloaded | 请求队列已达到 QUEUE_CAPACITY 而被填满(Retry-After 会被保留) |
其他任何 4xx 或 5xx 的工作方式都相同——为你的 PHP 应用返回的状态码,或为自定义的 PHP_DENY_FALLBACK 状态码,添加一个 403.html、451.html 等文件即可。唯一的例外是限流器的 429,它在本处理器运行之前就已生成,并始终使用其默认响应体(参见下方的说明)。
现成示例
一个最简的 404 页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>404 - Page Not Found</title>
<style>
body { font-family: system-ui, sans-serif; text-align: center; padding: 4rem 1rem; color: #333; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
p { color: #666; }
</style>
</head>
<body>
<h1>Page Not Found</h1>
<p>The page you requested does not exist.</p>
</body>
</html>一个带自动刷新的 503 维护页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="30">
<title>503 - Service Unavailable</title>
<style>
body { font-family: system-ui, sans-serif; text-align: center; padding: 4rem 1rem; color: #333; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
p { color: #666; }
</style>
</head>
<body>
<h1>Service Unavailable</h1>
<p>We are performing maintenance. This page will refresh automatically.</p>
</body>
</html>故障排查
自定义错误页面没有出现
确认 ERROR_PAGES_DIR 已设置,并且文件命名正确。
检查: 确认当前生效的目录路径,以及 OxPHP 在启动时记录了 "Loaded custom error page" 相关的日志行:
docker logs my-app 2>&1 | grep "error page"修复: 确保目录路径正确、文件名为 {status}.html,并且容器对该目录具有读取权限。
启动时出现关于错误页面目录缺失的警告
如果 ERROR_PAGES_DIR 目录不存在或无法读取,OxPHP 会记录一条警告并在没有自定义错误页面的情况下继续运行。此时错误响应会使用纯文本响应体。请检查 Docker 中的卷是否正确挂载:
docker run --rm -v ./errors:/var/www/errors:ro \
-e ERROR_PAGES_DIR=/var/www/errors \
ghcr.io/oxphp/oxphp:0.10.0429 响应仍然显示默认响应体
某些在响应流水线运行之前就生成的响应——例如限流拒绝——不会经过错误页面处理器处理。无论是否存在 429.html 文件,来自限流器的 429 Too Many Requests 响应都会使用其默认响应体。
Docker 示例
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "8080:8080"
volumes:
- ./src:/var/www/html:ro
- ./errors:/var/www/errors:ro
environment:
ERROR_PAGES_DIR: "/var/www/errors"
ENTRY_FILE: "index.php"目录结构:
project/
src/
public/
index.php
errors/
400.html
403.html
404.html
500.html
503.html
504.html
529.html最佳实践
- 让错误页面保持自包含并使用内联 CSS。不要引用外部样式表或脚本——这些二次请求本身可能会失败。
- 在
503.html上加入<meta http-equiv="refresh" content="30">标签,让用户在维护结束后自动重试。 - 让错误页面保持小巧。每个加载的页面在服务器进程的整个生命周期内都会驻留在内存中。
自定义错误页面适用于流经正常请求流水线的响应。来自限流器的 429 Too Many Requests 响应在错误页面处理器运行之前就已生成,并使用其默认的纯文本响应体。