Пользовательские страницы ошибок

OxPHP отдаёт фирменные HTML-страницы ошибок для ответов 4xx и 5xx. Каждая страница читается с диска один раз при запуске и отдаётся из памяти, поэтому во время обработки запроса никаких дисковых операций ввода-вывода не происходит.

Как это работает

  1. Загрузка при запуске. При запуске OxPHP читает каталог, указанный в ERROR_PAGES_DIR, и загружает в память каждый корректный файл {status}.html.
  2. Правила именования. Имя файла должно содержать числовой код состояния HTTP из диапазона 400–599 (например, 404.html, 503.html). Файлы с нечисловыми именами, кодами состояния вне этого диапазона (включая 200.html) или с расширением, отличным от .html, молча игнорируются.
  3. Замена тела. Когда OxPHP формирует ответ 4xx или 5xx, он проверяет наличие подходящей предзагруженной страницы ошибки. Если такая есть, OxPHP заменяет только тело и описывающие его заголовки: Content-Type устанавливается в text/html; charset=utf-8, Content-Length — в размер страницы, а заголовки, привязанные к исходному телу (Content-Encoding, ETag, Last-Modified), отбрасываются, чтобы они не могли неверно описать замену или инициировать её перепроверку (например, тело ошибки PHP, сжатое ob_gzhandler, не оставит HTML-страницу помеченной как Content-Encoding: gzip). Заголовки, описывающие семантику ответа, а не его тело, сохраняются в пользовательской странице: Content-Range для 416 Range Not Satisfiable, Retry-After для 529 Site is overloaded и Allow для 405 Method Not Allowed.
  4. Запасной вариант. Если каталог не существует или не может быть прочитан при запуске, OxPHP записывает предупреждение в лог и продолжает работу без пользовательских страниц ошибок. Ответы с ошибками откатываются к телам в виде обычного текста, пока каталог не будет исправлен и сервер не будет перезапущен.

Конфигурация

Переменная По умолчанию Описание
ERROR_PAGES_DIR (не задано) Каталог с HTML-файлами пользовательских страниц ошибок. Файлы должны называться {status}.html для кодов состояния 400–599. Если не задано, ответы с ошибками используют тела в виде обычного текста

Примеры страниц

Каждая страница ошибки — это самодостаточный HTML-файл с именем {status}.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 Запрос QUERY (RFC 10008), отправленный без заголовка Content-Type
404.html Not Found Нет подходящего файла или маршрута; заблокированный dotfile (.env, .git/); прямой запрос .php в режиме Framework; стандартный запасной вариант 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 работает так же: добавьте 403.html, 451.html и так далее для кодов, которые возвращает ваше PHP-приложение, или для собственного статуса PHP_DENY_FALLBACK. Единственное исключение — код 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, а у контейнера есть доступ на чтение к каталогу.

Предупреждение при запуске об отсутствующем каталоге страниц ошибок

OxPHP записывает предупреждение в лог и продолжает работу без пользовательских страниц ошибок, если каталог ERROR_PAGES_DIR не существует или не может быть прочитан. В этом случае ответы с ошибками используют тела в виде обычного текста. Проверьте, что том корректно смонтирован в 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 Too Many Requests от ограничителя частоты запросов использует своё тело по умолчанию независимо от того, присутствует ли файл 429.html.

Пример для 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. Не ссылайтесь на внешние таблицы стилей или скрипты — эти вторичные запросы сами могут завершиться неудачей.
  • Добавьте тег <meta http-equiv="refresh" content="30"> в 503.html, чтобы пользователи автоматически повторяли попытку после завершения обслуживания.
  • Держите страницы ошибок небольшими. Каждая загруженная страница хранится в памяти в течение всего времени жизни процесса сервера.
Note

Пользовательские страницы ошибок применяются к ответам, проходящим через обычный конвейер запросов. Ответ 429 Too Many Requests от ограничителя частоты запросов формируется до запуска обработчика страниц ошибок и использует своё тело по умолчанию в виде обычного текста.

Смотрите также