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

OxPHP маршрутизирует входящие HTTP-запросы с помощью одного из трёх режимов, управляемых единственной переменной окружения. Каждый режим повторяет привычную конфигурацию try_files в nginx, поэтому вы можете точно предсказать, что произойдёт с любым URL.

Как это работает

Каждый запрос проходит через общий конвейер, прежде чем вступит в действие логика конкретного режима:

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

Шаг классификации — ключ к эффективности: проверка диска для статических ресурсов (/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, и каждый из них отображается на эквивалентную конфигурацию try_files в nginx.

Активен, когда 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 — запасной вариант с корневым front-контроллером
  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

Разбиение PATH_INFO всегда включено в режиме Traditional. Переключателя через переменную окружения нет — прежний флаг SPLIT_PATH_INFO_ENABLED был удалён.

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

Режим воркеров активируется, когда WORKER_MODE_ENABLED=true и ENTRY_FILE указывает на .php-скрипт. Роутер раздаёт статические ресурсы с диска и направляет все остальные запросы к воркеру ENTRY_FILE — воркер является единственным front-контроллером.

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

Произвольные .php-файлы в корне документов никогда не выполняются напрямую в режиме воркеров — запрос к /about.php попадает в колбэк воркера, как и любой другой маршрут, даже если about.php существует на диске. Здесь также нет поиска индекса каталога и нет запасного варианта с корневым index.php; воркер сам видит эти запросы.

Два исключения, оба — защиты уровня сервера, срабатывающие до диспетчеризации режима: пути с dot-сегментами (/.git/config, /.env, голый /.well-known) отклоняются блокировкой dot-путей, а .php-URI внутри /.well-known/ отклоняются в рамках эшелонированной защиты. Оба возвращают 404 и никогда не достигают воркера.

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

  • WORKER_MODE_ENABLED=true без ENTRY_FILEWORKER_MODE_ENABLED=true requires ENTRY_FILE to be set.
  • WORKER_MODE_ENABLED=true с не-.php ENTRY_FILEWORKER_MODE_ENABLED=true requires a .php ENTRY_FILE.

Полные детали конфигурации см. в разделе Режим воркеров.

Поведение 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 (выполняемый скрипт) является буквальным префиксом пути запроса. Перезапись на front-контроллер, которую URL не называет явно — маршрут приложения, индекс каталога, запасной вариант при промахе по статике — не несёт PATH_INFO; вместо этого читайте REQUEST_URI. В режиме Traditional прежняя переменная окружения SPLIT_PATH_INFO_ENABLED была удалена.

Безопасность путей

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

  • Процентное декодирование выполняется до санитизации, поэтому закодированные попытки обхода вроде /%2e%2e/etc/passwd перехватываются
  • Фильтрация сегментов удаляет .., . и пустые сегменты из разрешённого пути
  • Проверка символических ссылок канонизирует каждый разрешённый путь и убеждается, что он остаётся внутри корня документов. Символические ссылки, указывающие за пределы раздаваемого каталога, блокируются
  • Блокировка dot-путей блокирует любой сегмент пути, начинающийся с . (например, /.git/config, /.env), за исключением /.well-known/* согласно RFC 8615
  • Блокировка PHP в well-known — даже при наличии исключения для dot-путей .php-скрипты внутри /.well-known/ никогда не выполняются (эшелонированная защита)
  • Дени-лист выполнения PHP — в режимах прямого сопоставления (Traditional и SPA) PHP_DENY_PATHS блокирует выполнение .php по заданным glob-шаблонам (например, /uploads/** или отдельный файл вроде /admin/legacy.php) до любого дискового ввода-вывода. См. Дени-лист выполнения PHP
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 отсутствующий статический файл откатывается к front-контроллеру (/index.php), поэтому роутер вашего приложения рендерит 404. Используйте режим SPA, если хотите жёсткие 404 для отсутствующих ресурсов.

Прямой /index.php больше не возвращает 404

В режиме Framework прямой доступ к front-контроллеру теперь разрешён (перезапись в /index.php идемпотентна). Если раньше вы полагались на 404 для обнаружения прямых обращений, переключитесь на проверку REQUEST_URI изнутри контроллера.

PATH_INFO пуст в режиме Framework

Это ожидаемо для маршрутов приложения. Режим Framework следует семантике CGI: PATH_INFO устанавливается только тогда, когда запрос явно называет входной файл с завершающим сегментом (/index.php/news/news). Для обычного маршрута приложения вроде /users/42 front-контроллер достигается внутренней перезаписью, которую он не называет, поэтому 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

См. также