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

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

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

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

  1. Фильтр dot-путей — пути, содержащие скрытые сегменты (.git, .env), блокируются, за исключением /.well-known/* (RFC 8615)
  2. Поиск в кэше маршрутов — недавно разрешённые URI возвращаются из LRU-кэша (10 000 записей)
  3. Процентное декодирование + санитизация — закодированные последовательности вроде %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, и каждый из них отображается на эквивалентную конфигурацию 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. Если файл .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 с не-.php ENTRY_FILE → 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 (выполняемый скрипт) является буквальным префиксом пути запроса. Перезапись на front-контроллер, которую URL не называет явно — маршрут приложения, индекс каталога, запасной вариант при промахе по статике — не несёт PATH_INFO; вместо этого читайте REQUEST_URI. В режиме Traditional прежняя переменная окружения SPLIT_PATH_INFO_ENABLED была удалена.

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

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

  • Процентное декодирование выполняется до санитизации, поэтому закодированные попытки обхода вроде /%2e%2e/etc/passwd перехватываются
  • Фильтрация сегментов удаляет .., . и пустые сегменты из разрешённого пути
  • Проверка символических ссылок канонизирует каждый разрешённый путь и убеждается, что он остаётся внутри корня документов. Символические ссылки, указывающие за пределы раздаваемого каталога, блокируются, в том числе перенаправленные уже после того, как путь был впервые отдан: статические файлы и PHP-скрипты проверяются повторно по фактически открытому файлу (см. Разрешённые пути для символьных ссылок)
  • Блокировка 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
  • Правила запрета путей — файл .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 отсутствующий статический файл откатывается к 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.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` вместе с тем блоком `try_files` из nginx, который они заменяют.
prompt
URL, которые работали под nginx, отдают 404 на OxPHP. Прочитай страницу `features/routing` через MCP-сервер документации OxPHP. Сравни режим, который выбирает моя конфигурация, с путями, которые этот репозиторий на самом деле обслуживает, — переписывание на фронт-контроллер, индексы каталогов, всё, что читает `PATH_INFO`, — и скажи мне, что из этого выбранный режим не покрывает.

См. также

Нашли ошибку? Сообщите →