自定义错误页面

OxPHP 为 4xx 和 5xx 响应提供带有品牌样式的 HTML 错误页面。每个页面在启动时从磁盘读取一次,随后从内存中提供,因此在请求处理期间不会发生任何磁盘 I/O。

工作原理

  1. 启动时加载。 在启动时,OxPHP 会读取 ERROR_PAGES_DIR 指定的目录,并将每个有效的 {status}.html 文件加载到内存中。
  2. 命名规则。 文件名必须是 400–599 范围内的数字 HTTP 状态码(例如 404.html503.html)。名称为非数字、状态码超出该范围(包括 200.html)或扩展名非 .html 的文件会被静默忽略。
  3. 替换响应体。 当 OxPHP 生成 4xx 或 5xx 响应时,它会检查是否存在匹配的预加载错误页面。如果存在,OxPHP 只替换响应体以及描述响应体的那些头部:Content-Type 被设为 text/html; charset=utf-8Content-Length 被设为页面大小,而与原始响应体绑定的头部(Content-EncodingETagLast-Modified)会被丢弃,以免它们对替换后的内容做出错误的标注或触发重新验证(例如,被 ob_gzhandler 压缩过的 PHP 错误响应体,不会让 HTML 页面残留 Content-Encoding: gzip 的标记)。那些描述响应语义而非响应体的头部则会被保留到自定义页面中——416 Range Not Satisfiable 上的 Content-Range529 Site is overloaded 上的 Retry-After,以及 405 Method Not Allowed 上的 Allow
  4. 回退。 如果目录在启动时不存在或无法读取,OxPHP 会记录一条警告并在没有自定义错误页面的情况下继续运行。错误响应会回退为纯文本响应体,直到目录问题修复且服务器重启为止。

配置

变量 默认值 说明
ERROR_PAGES_DIR (未设置) 包含自定义错误页面 HTML 文件的目录。文件名必须为对应 400–599 状态码的 {status}.html。未设置时,错误响应使用纯文本响应体

示例页面

每个错误页面都是一个名为 {status}.html 的自包含 HTML 文件。请让它们使用内联样式,不引用任何外部资源——否则一个失败的二次请求本身就会破坏错误页面。

可复用模板

将下面的内容复制到每个 {status}.html 中,并修改 <title><h1><p>

{status}.html
<!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.html451.html 等文件即可。唯一的例外是限流器的 429,它在本处理器运行之前就已生成,并始终使用其默认响应体(参见下方的说明)。

现成示例

一个最简的 404 页面:

404.html
<!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 维护页面:

503.html
<!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" 相关的日志行:

bash
docker logs my-app 2>&1 | grep "error page"

修复: 确保目录路径正确、文件名为 {status}.html,并且容器对该目录具有读取权限。

启动时出现关于错误页面目录缺失的警告

如果 ERROR_PAGES_DIR 目录不存在或无法读取,OxPHP 会记录一条警告并在没有自定义错误页面的情况下继续运行。此时错误响应会使用纯文本响应体。请检查 Docker 中的卷是否正确挂载:

bash
docker run --rm -v ./errors:/var/www/errors:ro \ -e ERROR_PAGES_DIR=/var/www/errors \ ghcr.io/oxphp/oxphp:0.10.0
429 响应仍然显示默认响应体

某些在响应流水线运行之前就生成的响应——例如限流拒绝——不会经过错误页面处理器处理。无论是否存在 429.html 文件,来自限流器的 429 Too Many Requests 响应都会使用其默认响应体。

Docker 示例

compose.yaml
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"

目录结构:

text
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"> 标签,让用户在维护结束后自动重试。
  • 让错误页面保持小巧。每个加载的页面在服务器进程的整个生命周期内都会驻留在内存中。
Note

自定义错误页面适用于流经正常请求流水线的响应。来自限流器的 429 Too Many Requests 响应在错误页面处理器运行之前就已生成,并使用其默认的纯文本响应体。

另请参阅

  • 路由 —— 未匹配路径的 404 响应是如何生成的
  • 限流 —— 限流行为与 429 响应
  • 配置参考 —— 完整的环境变量参考