カスタムエラーページ
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 自身が返すコードなので、それぞれにファイルを用意してください。
| ファイル | ステータス | OxPHP が返す条件 |
|---|---|---|
400.html |
Bad Request | Content-Type ヘッダーなしで送信された QUERY リクエスト(RFC 10008) |
404.html |
Not Found | 一致するファイルまたはルートがない場合、ブロックされたドットファイル(.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 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 で自己完結させてください。外部スタイルシートやスクリプトを参照しないでください。それらの二次リクエスト自体が失敗する可能性があります。
503.htmlには<meta http-equiv="refresh" content="30">タグを含めて、メンテナンス完了後にユーザーが自動的に再試行できるようにしてください。- エラーページは小さく保ってください。読み込まれた各ページは、サーバープロセスの存続期間中ずっとメモリに保持されます。
カスタムエラーページは、通常のリクエストパイプラインを流れるレスポンスに適用されます。レート制限からの 429 Too Many Requests レスポンスは、エラーページハンドラーが実行される前に生成され、デフォルトのプレーンテキストボディを使用します。