Маршрутизація

OxPHP маршрутизує вхідні HTTP-запити в одному з трьох режимів, який обирає ENTRY_FILE, а WORKER_MODE_ENABLED=true натомість перемикає маршрутизацію в режим воркерів. Кожен із трьох режимів відтворює знайому конфігурацію nginx try_files, тож ви можете точно передбачити, що станеться з будь-якою URL-адресою.

Як це працює

Кожен запит проходить спільний конвеєр, перш ніж вмикається логіка конкретного режиму:

  1. Фільтр шляхів із крапкою — шляхи з прихованими сегментами (.git, .env) блокуються, за винятком /.well-known/* (RFC 8615)
  2. Пошук у кеші маршрутів — нещодавно розв’язані URI повертаються з LRU-кешу (10 000 записів)
  3. Percent-декодування та очищення — закодовані послідовності на кшталт %2e%2e декодуються, а сегменти обходу (.., ., порожні) видаляються
  4. Правила заборони шляхів — якщо файл .oxphpdeny містить принаймні одне правило заборони або правило >, очищений шлях зіставляється ще до будь-якого звернення до диска: на правило заборони відповідає PHP_DENY_FALLBACK, правило > передає запит вхідному скрипту, а шлях, що виходить за обмеження файлу на глибину й довжину, отримує 404
  5. Блокування PHP у well-known — ешелонований захист: скрипти .php усередині /.well-known/ ніколи не виконуються
  6. Класифікація URI — очищений шлях один раз класифікується як NoExtension, Php або OtherExtension
  7. Диспетчеризація за режимом — кожен режим обробляє три типи URI за власними правилами
  8. Перевірка символьних посилань — кожен розв’язаний шлях файлової системи після канонікалізації має лишатися всередині кореня документів, а статичний файл чи PHP-скрипт перевіряється ще раз, на файлі, який фактично відкрито, коли його читають або виконують

Саме крок класифікації дає основний виграш в ефективності: перевірка диска для статичних ресурсів (/style.css, /logo.png) виконується один раз у спільному шарі для URI типу OtherExtension, тож усі три режими платять однакову ціну.

Налаштування

Змінна Типово Опис
DOCUMENT_ROOT /var/www/html/public Кореневий каталог для віддавання файлів і PHP-скриптів
ENTRY_FILE (не задано) Єдиний канонічний вхідний скрипт. Не задано — Traditional. *.php — Framework. Не .php — SPA. Разом із WORKER_MODE_ENABLED=true — Worker. Приймає абсолютний шлях або шлях відносно DOCUMENT_ROOT (.. дозволено); розв’язаний шлях має існувати
WORKER_MODE_ENABLED false Вмикає постійний режим воркерів. Потребує, щоб ENTRY_FILE вказував на скрипт .php

Застарілі змінні INDEX_FILE і WORKER_FILE досі розбираються (з попередженням WARN під час запуску) і відображаються на нову модель. Див. Налаштування → Застарілі.

Режими маршрутизації

Режими Traditional, Framework і SPA обираються змінною ENTRY_FILE за WORKER_MODE_ENABLED=false, і кожен відповідає еквівалентній конфігурації 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.php, PATH_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 Виконати api.php з PATH_INFO=/users/42
/missing.txt Перехід до /index.php
/some/route Перехід до /index.php

У режимі Traditional розділення PATH_INFO увімкнене завжди. Перемикача через змінну оточення немає — колишній прапорець SPLIT_PATH_INFO_ENABLED вилучено.

Режим воркерів

Режим воркерів вмикається, коли WORKER_MODE_ENABLED=true і ENTRY_FILE вказує на скрипт .php. Маршрутизатор віддає статичні ресурси з диска, а всі інші запити передає воркеру ENTRY_FILE — воркер є єдиним фронт-контролером.

Тип URI Поведінка
Статичні ресурси (.css, .png, … — будь-яке розширення, крім .php) Віддаються напряму з диска, якщо існують; відсутній ресурс передається воркеру ENTRY_FILE (а не жорстка 404)
Усе інше (URI .php, шляхи без розширення, /) Передається воркеру ENTRY_FILE

Довільні файли .php у корені документів у режимі воркерів ніколи не виконуються напряму — запит до /about.php потрапляє до колбеку воркера, як і будь-який інший маршрут, навіть якщо about.php існує на диску. Немає ані пошуку індексу каталогу, ані резервного кореневого index.php; такі запити воркер бачить сам.

Винятки — це захист на рівні сервера, що спрацьовує до диспетчеризації за режимом, і жоден із них не доходить до воркера. Шляхи з сегментами-крапками (/.git/config, /.env, голий /.well-known) відхиляються блокуванням шляхів із крапкою, а URI .php під /.well-known/ відхиляються як ешелонований захист, обидва — з 404. Якщо файл .oxphpdeny містить принаймні одне правило заборони або правило >, шлях, якому відповідає правило заборони, отримує статус PHP_DENY_FALLBACK, а шлях, що виходить за обмеження файлу на глибину й довжину, — 404.

Перевірка під час запуску відхиляє три комбінації:

  • WORKER_MODE_ENABLED=true без ENTRY_FILE → WORKER_MODE_ENABLED=true requires ENTRY_FILE to be set.
  • WORKER_MODE_ENABLED=true з ENTRY_FILE не .php → WORKER_MODE_ENABLED=true requires a .php ENTRY_FILE.
  • Правило заборони .oxphpdeny разом зі скриптом у PHP_DENY_FALLBACK → PHP_DENY_FALLBACK is a script, but in worker mode every request runs the worker entry. Потік воркера не виконує нічого, крім вхідного скрипта, тож не може виконати резервний скрипт замість забороненого запиту; використовуйте код статусу.

Повні подробиці налаштування — у розділі Режим воркерів.

Поведінка PATH_INFO

$_SERVER['PATH_INFO'] заповнюється по-різному залежно від режиму:

Режим Коли задається Значення
Traditional Лише коли URI містить .php/ (розділення PATH_INFO) Хвіст після сегмента скрипта, наприклад /users/42
Framework Лише для явного запиту /index.php/extra Хвіст після вхідного файлу, наприклад /news
SPA Ніколи (PHP викликається лише для точних файлів .php; без PATH_INFO)

PATH_INFO дотримується семантики CGI: він присутній лише тоді, коли SCRIPT_NAME (виконуваний скрипт) є буквальним префіксом шляху запиту. Переписування на фронт-контролер, який URL-адреса не називає, — маршрут застосунку, індекс каталогу, резервний перехід через відсутній статичний файл — не несе PATH_INFO; читайте натомість REQUEST_URI. У режимі Traditional колишню змінну оточення SPLIT_PATH_INFO_ENABLED вилучено.

Безпека шляхів

OxPHP застосовує кілька рівнів захисту від обходу каталогів, розкриття прихованих файлів і втечі через символьні посилання:

  • Percent-декодування виконується до очищення, тож закодовані спроби обходу на кшталт /%2e%2e/etc/passwd перехоплюються
  • Фільтрація сегментів видаляє з розв’язаного шляху .., . і порожні сегменти
  • Перевірка символьних посилань канонікалізує кожен розв’язаний шлях і перевіряє, що він лишається в межах кореня документів. Символьні посилання, що ведуть за межі каталогу, який віддається, блокуються, зокрема й ті, ціль яких змінили після того, як шлях уперше було віддано: статичні файли й PHP-скрипти перевіряються ще раз на файлі, який фактично відкрито (див. Дозволені шляхи для символьних посилань)
  • Блокування шляхів із крапкою блокує будь-який сегмент шляху, що починається з . (наприклад, /.git/config, /.env), за винятком /.well-known/* згідно з RFC 8615
  • Блокування PHP у well-known — навіть попри виняток для шляхів із крапкою, скрипти .php під /.well-known/ ніколи не виконуються (ешелонований захист)
  • Список заборони виконання PHP — у режимах прямого відображення (Traditional і SPA) PHP_DENY_PATHS блокує виконання .php за налаштованими glob-шаблонами (наприклад, /uploads/** або окремий файл на кшталт /admin/legacy.php) ще до будь-якого дискового вводу-виводу. Див. Список заборони виконання PHP
  • Правила заборони шляхів — файл .oxphpdeny на верхньому рівні DOCUMENT_ROOT перелічує в синтаксисі gitignore шляхи, які ніколи не віддаються (як статичні файли, так і скрипти) або, з префіксом >, передаються вхідному скрипту. Він діє в кожному режимі маршрутизації одразу після того, як шлях декодовано й нормалізовано, і ще до будь-якого звернення до диска
Note

Якщо каталог кореня документів не існує під час запуску, сервер завершує роботу з фатальною помилкою. Захист від втечі через символьні посилання потребує дійсного шляху до кореня документів, який можна розв’язати.

Усунення несправностей

Усі запити повертають 404 у режимі Traditional

Перевірте, що в корені документів є index.php або index.html. Ланцюжок try_files режиму Traditional переходить лише до них — якщо обидва відсутні й жоден файл не відповідає URL-адресі, ви отримаєте 404.

bash
docker exec <container> ls /var/www/html/public
Відсутній статичний ресурс повертає 404 замість оболонки SPA

У режимі SPA це навмисно: відсутній /style.css — жорстка 404, а не непомітний перехід до index.html, що дає змогу рано виявляти биті посилання на ресурси. У режимах Framework і Traditional відсутній статичний файл передається фронт-контролеру (/index.php), тож 404 рендерить маршрутизатор вашого застосунку. Використовуйте режим SPA, якщо хочете жорсткі 404 для відсутніх ресурсів.

Прямий запит до /index.php більше не повертає 404

У режимі Framework прямий доступ до фронт-контролера тепер дозволено (переписування на /index.php ідемпотентне). Якщо раніше ви покладалися на 404, щоб виявляти прямі звернення, перевіряйте натомість REQUEST_URI усередині контролера.

PATH_INFO порожній у режимі Framework

Для маршрутів застосунку це очікувано. Режим Framework дотримується семантики CGI: PATH_INFO задається лише тоді, коли запит явно називає вхідний файл із додатковим сегментом (/index.php/news → /news). Для звичайного маршруту застосунку на кшталт /users/42 фронт-контролер досягається внутрішнім переписуванням, якого URL-адреса не називає, тож PATH_INFO відсутній — читайте шлях із $_SERVER['REQUEST_URI']. (Якщо ENTRY_FILE не закінчується на .php, OxPHP обирає режим SPA, який ніколи не заповнює PATH_INFO.)

Символьне посилання всередині кореня документів повертає 404

Символьні посилання, що ведуть за межі кореня документів, блокуються за задумом. Перенесіть цільовий вміст у корінь документів або змонтуйте його як каталог за потрібним шляхом.

Приклад для Docker

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

Промпти для вашого агента

Ці промпти розраховані на агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.

prompt
Обери правильний режим маршрутизації OxPHP для цього проєкту. Прочитай сторінку `features/routing` через MCP-сервер документації OxPHP. Визнач за фреймворком і коренем документів у цьому репозиторії, якому режиму він відповідає, і дай мені значення `DOCUMENT_ROOT` та `ENTRY_FILE` разом із блоком nginx `try_files`, який вони замінюють.
prompt
URL-адреси, які працювали під nginx, на OxPHP повертають 404. Прочитай сторінку `features/routing` через MCP-сервер документації OxPHP. Порівняй режим, який обирає моя конфігурація, зі шляхами, які цей репозиторій насправді обслуговує, — переписування на фронт-контролер, індекси каталогів, усе, що читає `PATH_INFO`, — і скажи, які з них цей режим не покриває.

Див. також