路由

OxPHP 使用三种模式之一来路由传入的 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. 模式派发 — 每种模式用各自的规则处理这三类 URI
  7. 符号链接校验 — 每个解析出的文件系统路径都必须规范化到文档根目录之内

分类这一步是效率的关键:对静态资源(/style.css/logo.png)的磁盘检查在共享层中对 OtherExtension 类 URI 只做一次,因此三种模式付出的代价相同。

配置

变量 默认值 说明
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 执行 api.phpPATH_INFO=/users/42
/missing.txt 回退到 /index.php
/some/route 回退到 /index.php

在传统模式下,PATH_INFO 拆分始终开启。没有环境变量开关——此前的 SPLIT_PATH_INFO_ENABLED 标志已被移除。

工作进程模式

工作进程模式在 WORKER_MODE_ENABLED=trueENTRY_FILE 指向一个 .php 脚本时激活。路由器从磁盘提供静态资源,并把所有其他请求派发给工作进程的 ENTRY_FILE——该工作进程就是唯一的前端控制器。

URI 类别 行为
静态资源(.css.png……——任何非 .php 扩展名) 若存在则直接从磁盘提供;缺失的资源会贯穿到工作进程的 ENTRY_FILE(而非硬 404)
其他一切(.php URI、无扩展名路径、/ 派发给工作进程的 ENTRY_FILE

在工作进程模式下,文档根目录中任意的 .php 文件永不被直接执行——即使 about.php 在磁盘上存在,对 /about.php 的请求也会像任何其他路由一样到达工作进程回调。同样没有目录索引查找,也没有根 index.php 回退;这些请求都会由工作进程自己接收。

有两个例外,都是在模式派发之前运行的服务器级防御:点段路径(/.git/config/.env、裸的 /.well-known)会被 点路径拦截 拒绝,而 /.well-known/ 下的 .php URI 会作为纵深防御被拒绝。两者都返回 404,永远不会到达工作进程。

启动时的校验会拒绝两种组合:

  • WORKER_MODE_ENABLED=true 但未设置 ENTRY_FILEWORKER_MODE_ENABLED=true requires ENTRY_FILE to be set
  • WORKER_MODE_ENABLED=trueENTRY_FILE 为非 .phpWORKER_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 会在任何磁盘 I/O 之前,在配置的 glob 模式(例如 /uploads/**,或像 /admin/legacy.php 这样的单个文件)处拦截 .php 执行。参见 PHP 执行拒绝列表
Note

如果文档根目录在启动时不存在,服务器会以致命错误退出。符号链接逃逸保护需要一个有效、可解析的文档根路径。

故障排查

传统模式下所有请求都返回 404

检查文档根目录下是否存在 index.phpindex.html。传统模式的 try_files 链只会回退到这两者——如果两者都缺失且没有文件匹配该 URL,你就会得到 404。

bash
docker exec <container> ls /var/www/html/public
缺失的静态资源返回 404 而不是 SPA 外壳

这在 SPA 模式下是刻意为之:缺失的 /style.css 是一个硬 404,而不是悄悄回退到 index.html,这能尽早发现损坏的资源引用。在框架模式和传统模式下,缺失的静态文件会回退到前端控制器(/index.php),因此由你的应用路由器渲染 404。如果你希望缺失资源返回硬 404,请使用 SPA 模式。

直接访问 /index.php 不再返回 404

在框架模式下,现在允许直接访问前端控制器(到 /index.php 的重写是幂等的)。如果你此前依赖 404 来检测直接访问,请改为在控制器内部检查 REQUEST_URI

框架模式下 PATH_INFO 为空

这对于应用路由是预期行为。框架模式遵循 CGI 语义:只有当请求显式指定带尾部段的入口文件时(/index.php/news/news)才会设置 PATH_INFO。对于像 /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

另请参阅