カスタムエラーページ

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-Typetext/html; charset=utf-8 に、Content-Length はページサイズに設定され、元のボディに結びついたヘッダー(Content-EncodingETagLast-Modified)は削除されます。これにより、置き換えたコンテンツを誤って表示したり再検証したりできないようにします(例えば ob_gzhandler で圧縮された PHP エラーボディがあっても、HTML ページに Content-Encoding: gzip というタグが残ることはありません)。ボディではなくレスポンスのセマンティクスを記述するヘッダーは、カスタムページにも引き継がれます。416 Range Not SatisfiableContent-Range529 Site is overloadedRetry-After405 Method Not AllowedAllow などです。
  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 自身が返すコードなので、それぞれにファイルを用意してください。

ファイル ステータス 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.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 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 で自己完結させてください。外部スタイルシートやスクリプトを参照しないでください。それらの二次リクエスト自体が失敗する可能性があります。
  • 503.html には <meta http-equiv="refresh" content="30"> タグを含めて、メンテナンス完了後にユーザーが自動的に再試行できるようにしてください。
  • エラーページは小さく保ってください。読み込まれた各ページは、サーバープロセスの存続期間中ずっとメモリに保持されます。
Note

カスタムエラーページは、通常のリクエストパイプラインを流れるレスポンスに適用されます。レート制限からの 429 Too Many Requests レスポンスは、エラーページハンドラーが実行される前に生成され、デフォルトのプレーンテキストボディを使用します。

関連項目