浏览器发 options 预检请求但后端未收到,主因是 web 服务器(nginx/apache)或 api 网关拦截了 options 方法,未转发给 php;php 脚本也需主动响应而非仅设 cors 头,且须避免 credentials 与通配符 origin 冲突、header 漏配、响应体污染等问题。

为什么浏览器发了 OPTIONS 请求但后端没收到
常见现象是前端发 fetch 或 axios 带自定义 Header(比如 Authorization)或非简单方法(PUT/DELETE)时,浏览器自动先发一个 OPTIONS 预检请求,而 PHP 脚本默认不处理该请求 —— 尤其是用了 Nginx/Apache 的静态路由规则、或框架未启用中间件拦截时,OPTIONS 直接 404 或被 Web 服务器拒绝。
关键点:预检请求不带 Cookie、不带认证头,且要求服务端在响应头中明确声明允许的来源、方法、Header;PHP 脚本必须主动响应它,不能只靠 header() 设置 CORS 就完事。
- Apache 下需确认
mod_rewrite或mod_headers已启用,且.htaccess没把OPTIONS拦截掉 - Nginx 中若配置了
try_files $uri $uri/ /index.php?$query_string;,默认会把OPTIONS转给 PHP,但若加了limit_except或if ($request_method !~ ^(GET|HEAD|POST)$) { return 405; }这类限制,就会直接拒掉 - 纯 PHP CLI 或内置服务器(
php -S)完全不支持OPTIONS自动响应,必须手写逻辑
PHP 中最简可靠的 OPTIONS 响应写法
不依赖框架、不引入额外库,用原生 PHP 在入口文件(如 index.php)开头快速兜底:
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: https://your-frontend.com');
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');
header('Access-Control-Max-Age: 86400');
exit(0);
}
注意几个易错细节:
立即学习“PHP免费学习笔记(深入)”;
-
Access-Control-Allow-Origin不能设为*同时又开启Access-Control-Allow-Credentials: true,否则浏览器直接报错;必须写明确域名 -
Access-Control-Allow-Headers必须包含前端实际发送的所有自定义 Header,比如用了Bearer xxx就得有Authorization;漏一个,预检就失败 - 务必调用
exit或die,避免后续代码继续执行导致响应体污染(比如输出空格、HTML、错误信息) - 如果项目已用 Composer 加了
nikic/fast-route或slim/slim,别在全局加这段,应在路由定义里对OPTIONS单独注册空处理器
Laravel / ThinkPHP 等框架中的典型遗漏点
框架自带 CORS 中间件往往只处理「主请求」,不处理预检;或者中间件注册顺序不对,导致 OPTIONS 根本没走到中间件层。
- Laravel:确保
App\Http\Middleware\HandleCors在$middleware数组中,且位置靠前;检查config/cors.php中'allowed_methods'包含OPTIONS(虽然规范不要求返回它,但某些客户端会校验) - ThinkPHP 6:默认不拦截
OPTIONS,需手动在app/middleware.php添加闭包中间件,或在app/common.php中用think\Response::create('', 204)->header(...)->send()提前响应 - 所有框架:如果用了 API 网关、Nginx 重写规则(如把
/api/*代理到 PHP-FPM),要确认网关或 Nginx 层没提前返回 405 —— 此时 PHP 根本收不到请求
调试 OPTIONS 请求的三个实操动作
别只盯着 PHP 日志,预检失败往往是链路某一层静默吞掉了请求。
- 用
curl -X OPTIONS -I https://your-api.com/v1/user直接测服务端响应头,看是否返回200或204,以及是否有完整 CORS 头 - 在 Chrome DevTools 的 Network 面板中,筛选
Other或直接搜OPTIONS,点开看 Initiator 列——如果是fetch调用触发的,再点开该 fetch 的 Headers,对比Request Headers里的Access-Control-Request-Method和Access-Control-Request-Headers,确保你的 PHP 响应头里Allow-Methods和Allow-Headers完全覆盖 - 在 Nginx 的
error.log中加log_format options '$request_method $status $body_bytes_sent'; access_log /var/log/nginx/options.log options if=$is_options;,配合map $request_method $is_options { OPTIONS 1; default 0; },可独立捕获所有 OPTIONS 流量走向
跨域预检不是“加几个 header 就行”的问题,而是请求生命周期里一个独立的 HTTP 事务;漏掉任何一层的显式响应,都会让整个请求卡在 OPTIONS 阶段不动。真正麻烦的往往不是 PHP 本身,而是它前面那层你没意识到正在拦截它的东西。











