ThinkPHP 8 的 OPTIONS 请求必须在 Web 服务器层或中间件入口显式拦截,Route::options() 无效且不推荐;Nginx/Apache 需提前响应,PHP 环境应在 CorsMiddleware::handle() 开头判断并立即返回 204 响应及 CORS 头。

ThinkPHP 8 的 OPTIONS 请求必须显式拦截,不能靠路由规则匹配
ThinkPHP 默认不把 OPTIONS 当作可路由的 HTTP 方法处理,Route::get()、Route::post() 等定义完全不覆盖它。你写 Route::options('api/user', 'Api/User/options') 是无效的——框架压根不会进控制器,更不会执行方法体。
真实情况是:浏览器发的预检请求在到达路由分发层前,就可能被 Web 服务器(Nginx/Apache)直接拦下返回 405,或者被框架中间件链外的逻辑丢弃。所以「单独处理」不是指写个路由,而是提前卡在请求入口或中间件第一环。
- Apache 需在
.htaccess或虚拟主机配置里加RewriteCond %{REQUEST_METHOD} OPTIONS+RewriteRule转给index.php - Nginx 必须在
location块中用if ($request_method = 'OPTIONS')直接响应,不走 PHP - 如果坚持全由 PHP 处理(比如 Swoole 环境),就必须在
index.php开头或CorsMiddleware::handle()最前面判断$request->isOptions()
为什么 Route::options() 在 ThinkPHP 6/8 中基本没用
官方文档里几乎找不到 Route::options() 的有效用例,原因很实际:它只注册一个「能被 dispatch 到的路由」,但预检请求的语义是「试探是否允许后续请求」,根本不该触发业务逻辑(如数据库查询、鉴权、日志记录)。一旦进了控制器,性能白耗,还可能因未登录返回 401,导致整个跨域失败。
-
Route::options()定义后,仍需手动在控制器里return response('', 204),否则默认会尝试渲染模板或报错 - 即使返回了 204,CORS 头(如
Access-Control-Allow-Origin)也得手动$response->header()设置,框架不自动注入 - 若同时启用了
allowCrossDomain()路由分组,两个地方设的头可能冲突,比如一个写死域名,一个写*,浏览器会拒绝
CorsMiddleware 中怎么安全拦截并返回 OPTIONS
这是目前最可控、最推荐的方式,尤其适合需要动态校验 Origin 或统一管理 CORS 策略的项目。关键不是“处理”,而是“立即终止”。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
立即学习“PHP免费学习笔记(深入)”;
在 app/middleware/CorsMiddleware.php 的 handle() 方法开头写:
if ($request->isOptions()) {
$origin = $request->header('origin');
$allowed = config('cors.allow_origins', []);
if (empty($origin) || !in_array($origin, $allowed)) {
return response('', 403);
}
return response('', 204)
->header('Access-Control-Allow-Origin', $origin)
->header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
->header('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With')
->header('Access-Control-Allow-Credentials', 'true');
}
- 必须放在
return $next($request)之前,否则后续中间件或控制器已开始执行 -
Access-Control-Allow-Origin不能写*同时设Access-Control-Allow-Credentials: true,浏览器直接拒绝 - 如果用 Swoole,别用
header()函数,只用$response->header() - 不要在
return $next($request)之后再调$response->header(),响应体已生成,头写不进去
Web 服务器层直接响应 OPTIONS 更快,但灵活性差
如果你的前端域名固定、不需要动态 Origin 校验,Nginx 层直接响应是最省资源的方式。它连 PHP 解释器都不启动,毫秒级返回。
Nginx 配置示例(放在 location ~ \.php$ 同级):
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://your-frontend.com";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials "true";
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
- 注意:Nginx 的
add_header在if块里是“有条件添加”,不是覆盖;多个if不会叠加,要确保只有一处生效 - Apache 的
RewriteRule必须带[QSA,L],否则查询参数丢失,可能导致后续接口出错 - 这种写法无法做 Origin 白名单校验,也不支持
credentials: true下的动态 Origin 回写,纯静态场景才适用
$_SERVER['REQUEST_METHOD']。先确认服务器是否放行,再谈框架怎么处理。


















