Пользовательские страницы ошибок
OxPHP отдаёт фирменные HTML-страницы ошибок для ответов 4xx и 5xx. Каждая страница читается с диска один раз при запуске и отдаётся из памяти, поэтому во время обработки запроса никаких дисковых операций ввода-вывода не происходит.
Как это работает
- Загрузка при запуске. При запуске OxPHP читает каталог, указанный в
ERROR_PAGES_DIR, и загружает в память каждый корректный файл{status}.html. - Правила именования. Имя файла должно содержать числовой код состояния HTTP из диапазона 400–599 (например,
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), отбрасываются, чтобы они не могли неверно описать замену или инициировать её перепроверку (например, тело ошибки PHP, сжатоеob_gzhandler, не оставит HTML-страницу помеченной какContent-Encoding: gzip). Заголовки, описывающие семантику ответа, а не его тело, сохраняются в пользовательской странице:Content-Rangeдля416 Range Not Satisfiable,Retry-Afterдля529 Site is overloadedиAllowдля405 Method Not Allowed. - Запасной вариант. Если каталог не существует или не может быть прочитан при запуске, OxPHP записывает предупреждение в лог и продолжает работу без пользовательских страниц ошибок. Ответы с ошибками откатываются к телам в виде обычного текста, пока каталог не будет исправлен и сервер не будет перезапущен.
Конфигурация
| Переменная | По умолчанию | Описание |
|---|---|---|
ERROR_PAGES_DIR |
(не задано) | Каталог с HTML-файлами пользовательских страниц ошибок. Файлы должны называться {status}.html для кодов состояния 400–599. Если не задано, ответы с ошибками используют тела в виде обычного текста |
Примеры страниц
Каждая страница ошибки — это самодостаточный HTML-файл с именем {status}.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 | Запрос 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:
<!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, а у контейнера есть доступ на чтение к каталогу.
Предупреждение при запуске об отсутствующем каталоге страниц ошибок
OxPHP записывает предупреждение в лог и продолжает работу без пользовательских страниц ошибок, если каталог ERROR_PAGES_DIR не существует или не может быть прочитан. В этом случае ответы с ошибками используют тела в виде обычного текста. Проверьте, что том корректно смонтирован в Docker:
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
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. Не ссылайтесь на внешние таблицы стилей или скрипты — эти вторичные запросы сами могут завершиться неудачей.
- Добавьте тег
<meta http-equiv="refresh" content="30">в503.html, чтобы пользователи автоматически повторяли попытку после завершения обслуживания. - Держите страницы ошибок небольшими. Каждая загруженная страница хранится в памяти в течение всего времени жизни процесса сервера.
Пользовательские страницы ошибок применяются к ответам, проходящим через обычный конвейер запросов. Ответ 429 Too Many Requests от ограничителя частоты запросов формируется до запуска обработчика страниц ошибок и использует своё тело по умолчанию в виде обычного текста.
Смотрите также
- Маршрутизация — как формируются ответы 404 для несопоставленных путей
- Ограничение частоты запросов — поведение ограничения частоты запросов и ответы 429
- Справочник по конфигурации — полный справочник по переменным окружения