ルーティング

OxPHP は、単一の環境変数で制御される3つのモードのいずれかを使って、受信した HTTP リクエストをルーティングします。各モードは馴染みのある nginx の try_files 設定を再現しているため、どの URL に対しても何が起こるかを正確に予測できます。

仕組み

すべてのリクエストは、モード固有のロジックが動き出す前に、共通のパイプラインを通過します。

  1. ドットパスフィルター — 隠しセグメント(.git.env)を含むパスはブロックされます。ただし /.well-known/* は例外です(RFC 8615
  2. ルートキャッシュの検索 — 最近解決された URI は LRU キャッシュ(10 000 エントリ)から返されます
  3. パーセントデコード + サニタイズ%2e%2e のようなエンコードされたシーケンスがデコードされ、トラバーサルセグメント(...、空)が除去されます
  4. well-known 内の PHP ブロック — 多層防御として、/.well-known/ 内の .php スクリプトは決して実行されません
  5. URI の分類 — サニタイズされたパスは、一度だけ NoExtensionPhpOtherExtension のいずれかに分類されます
  6. モードディスパッチ — 各モードは、それぞれ独自のルールで3種類の URI を処理します
  7. シンボリックリンクの検証 — 解決されたすべてのファイルシステムパスは、ドキュメントルート内に正規化されなければなりません

分類ステップが効率化の鍵です。静的アセット(/style.css/logo.png)のディスクチェックは、OtherExtension の URI に対して共通レイヤーで一度だけ実行されるため、3つのモードすべてが同じコストで済みます。

設定

変数 デフォルト 説明
DOCUMENT_ROOT /var/www/html/public ファイルと PHP スクリプトを提供するルートディレクトリ
ENTRY_FILE (未設定) 単一の正規エントリースクリプト。未設定 = 従来型。*.php = フレームワーク。非 .php = SPA。WORKER_MODE_ENABLED=true を併用 = ワーカー。絶対パス、または DOCUMENT_ROOT からの相対パス(.. 可)を受け付けます。解決されたパスは存在しなければなりません
WORKER_MODE_ENABLED false 永続的なワーカーモードを有効にします。ENTRY_FILE.php スクリプトを指している必要があります

レガシーの INDEX_FILEWORKER_FILE 変数は今も解析され(起動時に WARN を出します)、新しいモデルへマッピングされます。設定 → 非推奨を参照してください。

ルーティングモード

従来型、フレームワーク、SPA の各モードは、WORKER_MODE_ENABLED=false の状態で ENTRY_FILE によって選択され、それぞれが同等の nginx try_files 設定にマッピングされます。

ENTRY_FILE未設定(または空)で、かつ WORKER_MODE_ENABLED=false のときに有効になります。同等の nginx 設定:

nginx
location / { try_files $uri $uri/ /index.php /index.html =404; } location ~ \.php$ { try_files $uri =404; # PATH_INFO splitting enabled }

解決順序:

  1. $uri — ディスク上の正確なファイル → 提供(.php なら実行)
  2. $uri/ — ディレクトリ → その中の index.php、次に index.html を探す
  3. PATH_INFO 分割 — URI に .php/ が含まれる場合、スクリプトのプレフィックスがディスク上でマッチされ、残りが PATH_INFO になります(例: /api.php/users/42 → スクリプト api.phpPATH_INFO=/users/42
  4. /index.php — ルートのフロントコントローラーへのフォールバック
  5. /index.html — ルートの静的インデックスへのフォールバック
  6. 404

例:

リクエスト 結果
/about.php about.php を実行
/style.css style.css を提供
/blog/blog/index.php あり) blog/index.php を実行
/api.php/users/42 PATH_INFO=/users/42api.php を実行
/missing.txt /index.php にフォールバック
/some/route /index.php にフォールバック

PATH_INFO 分割は従来型モードでは常に有効です。環境変数のスイッチはありません。以前の SPLIT_PATH_INFO_ENABLED フラグは削除されました。

ワーカーモード

ワーカーモードは、WORKER_MODE_ENABLED=true かつ ENTRY_FILE.php スクリプトを指しているときに有効になります。ルーターは静的アセットをディスクから提供し、それ以外のすべてのリクエストをワーカーの ENTRY_FILE にディスパッチします。ワーカーが唯一のフロントコントローラーです。

URI の種類 動作
静的アセット(.css.png、… — .php 以外の任意の拡張子) 存在すればディスクから直接提供。不在のアセットはワーカーの ENTRY_FILE にフォールスルー(ハードな 404 ではありません)
それ以外すべて(.php の URI、拡張子なしのパス、/ ワーカーの ENTRY_FILE にディスパッチ

ワーカーモードでは、ドキュメントルート内の任意の .php ファイルが直接実行されることは決してありません/about.php へのリクエストは、たとえ about.php がディスク上に存在していても、他のルートと同様にワーカーのコールバックに到達します。ディレクトリインデックスの検索もなく、ルートの index.php フォールバックもありません。ワーカー自身がそれらのリクエストを受け取ります。

例外が2つあり、いずれもモードディスパッチの前に動作するサーバーレベルの防御です。ドットセグメントのパス(/.git/config/.env、裸の /.well-known)はドットパスブロッキングによって拒否され、/.well-known/ 下の .php の URI は多層防御として拒否されます。どちらも 404 を返し、ワーカーに到達することはありません。

起動時の検証では、次の2つの組み合わせが拒否されます。

  • ENTRY_FILE なしの WORKER_MODE_ENABLED=trueWORKER_MODE_ENABLED=true requires ENTRY_FILE to be set
  • .php 以外の ENTRY_FILE を伴う WORKER_MODE_ENABLED=trueWORKER_MODE_ENABLED=true requires a .php ENTRY_FILE

完全な設定の詳細についてはワーカーモードを参照してください。

PATH_INFO の動作

$_SERVER['PATH_INFO'] は、モードによって異なる方法で設定されます。

モード 設定されるとき
従来型 URI に .php/ が含まれるときのみ(PATH_INFO 分割) スクリプトセグメントの後の末尾部分、例: /users/42
フレームワーク 明示的な /index.php/extra リクエストのときのみ エントリーファイルの後の末尾部分、例: /news
SPA 決して設定されない (PHP は正確な .php ファイルに対してのみ呼び出されるため、PATH_INFO はありません)

PATH_INFO は CGI のセマンティクスに従います。実行されたスクリプトである SCRIPT_NAME がリクエストパスのリテラルなプレフィックスである場合にのみ存在します。URL が指名しないフロントコントローラーのリライト(アプリケーションのルート、ディレクトリインデックス、静的ミスのフォールバック)は PATH_INFO を持ちません。代わりに REQUEST_URI を読み取ってください。従来型モードでは、以前の SPLIT_PATH_INFO_ENABLED 環境変数は削除されました。

パスのセキュリティ

OxPHP は、ディレクトリトラバーサル、隠しファイルの開示、シンボリックリンクによるエスケープ攻撃を防ぐために、複数の保護レイヤーを適用します。

  • パーセントデコードはサニタイズの前に実行されるため、/%2e%2e/etc/passwd のようなエンコードされたトラバーサルの試みが捕捉されます
  • セグメントフィルタリングは、解決されたパスから ...、空のセグメントを除去します
  • シンボリックリンクの検証は、解決されたすべてのパスを正規化し、それがドキュメントルート内に留まっていることを確認します。提供対象のディレクトリの外を指すシンボリックリンクはブロックされます
  • ドットパスブロッキングは、. で始まる任意のパスセグメント(例: /.git/config/.env)をブロックします。ただし RFC 8615 に従い /.well-known/* は例外です
  • well-known 内の PHP ブロック — ドットパスの例外があっても、/.well-known/ 下の .php スクリプトは決して実行されません(多層防御)
  • PHP 実行の拒否リスト — 直接マッピングモード(従来型と SPA)では、PHP_DENY_PATHS が、設定された glob パターン(例: /uploads/**、または /admin/legacy.php のような単一ファイル)における .php の実行を、ディスク I/O の前にブロックします。PHP 実行の拒否リストを参照してください
Note

起動時にドキュメントルートのディレクトリが存在しない場合、サーバーは致命的なエラーで終了します。シンボリックリンクのエスケープ保護には、有効で解決可能なドキュメントルートのパスが必要です。

トラブルシューティング

従来型モードですべてのリクエストが 404 を返す

ドキュメントルートに index.php または index.html が存在することを確認してください。従来型モードの try_files チェーンはこれらにのみフォールバックします。両方が不在で、かつ URL にマッチするファイルがない場合、404 が返されます。

bash
docker exec <container> ls /var/www/html/public
不在の静的アセットが SPA シェルではなく 404 を返す

これは SPA モードでは意図的な動作です。不在の /style.css は、index.html への黙ったフォールバックではなくハードな 404 となり、壊れたアセット参照を早期に検出できます。フレームワークモードと従来型モードでは、不在の静的ファイルはフロントコントローラー(/index.php)にフォールバックするため、アプリケーションのルーターが 404 をレンダリングします。不在のアセットに対してハードな 404 が欲しい場合は SPA モードを使用してください。

直接の /index.php がもう 404 を返さない

フレームワークモードでは、フロントコントローラーへの直接アクセスが許可されるようになりました(/index.php へのリライトは冪等です)。以前、直接アクセスを検出するために 404 に依存していた場合は、コントローラー内から REQUEST_URI をチェックする方法に切り替えてください。

フレームワークモードで PATH_INFO が空

これはアプリケーションのルートでは想定どおりの動作です。フレームワークモードは CGI のセマンティクスに従います。PATH_INFO は、リクエストが末尾セグメント付きでエントリーファイルを明示的に指名した場合(/index.php/news/news)にのみ設定されます。/users/42 のような通常のアプリルートでは、フロントコントローラーは指名しない内部リライトによって到達されるため、PATH_INFO はありません。パスは $_SERVER['REQUEST_URI'] から読み取ってください。(ENTRY_FILE.php で終わらない場合、OxPHP は SPA モードを選択し、PATH_INFO を一切設定しません。)

ドキュメントルート内のシンボリックリンクが 404 を返す

ドキュメントルートの外を指すシンボリックリンクは、設計上ブロックされます。ターゲットのコンテンツをドキュメントルート内に移動するか、正しいパスにディレクトリとしてマウントしてください。

Docker の例

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.10.0 ports: - "8080:80" volumes: - ./src:/var/www/html environment: - DOCUMENT_ROOT=/var/www/html/public - ENTRY_FILE=index.php

関連項目