ThinkPHP 6 响应头设置不成功主因是未在正确时机操作$response实例:必须在$next($request)之后、return之前调用$response->header(),且中间件须置于app/middleware.php首位,避免被后续中间件覆盖或框架重置。

响应头设置不成功,绝大多数情况不是代码写错了,而是头根本没来得及发出去——被框架重置、被中间件跳过、或被提前输出截断了。
为什么 $response->header() 看似执行了却没生效
ThinkPHP 6 使用 PSR-7 响应对象,header() 函数在 Swoole/Workerman 或多数中间件场景下完全无效;必须操作 $response 实例。但光调用 $response->header() 不够,关键在执行时机:
- 不能在
$next($request)之前设置——那时$response还没生成 - 不能在
$next($request)之后、但又在 return 之前被其他中间件覆盖(比如日志中间件修改了响应) - 如果控制器里用了
exit、die、echo或未捕获的异常,中间件链会中断,header 就丢了 - 某些中间件(如
ViewInit)会强制把返回值当视图渲染,导致json()被当成变量塞进 HTML 模板里,最终 Content-Type 变成text/html
中间件里设了 header,但浏览器看不到 Access-Control-Allow-Origin
这是最典型的“头被覆盖”现象。ThinkPHP 在最终发送响应前会重置部分手动设置的 header,尤其当你用了 response()->json() 或 return json() 这类封装方法时:
- 确保跨域中间件在
app/middleware.php中排在**第一位**:\think\middleware\Cors::class必须是数组首个元素 - 不要在控制器里再调用
header('Access-Control-Allow-Origin: *')—— 它会被框架丢弃,还可能触发headers already sent - 如果用了自定义中间件,必须在
$next($request)之后获取$response,再链式调用->header(),例如:return $response->header([...]) - 检查是否启用了
debug模式:开启时异常堆栈会提前输出内容,导致 header 发送失败
OPTIONS 预检通过了,但实际请求仍报 CORS 错误
这说明预检阶段的响应头(Access-Control-Allow-Headers、Access-Control-Allow-Methods)和主请求的响应头不一致,浏览器按最严格的一次校验:
立即学习“PHP免费学习笔记(深入)”;
-
Access-Control-Allow-Headers不能写*,尤其当启用了credentials时——必须显式列出前端真实发送的 header,比如Authorization、X-Auth-Token,大小写需完全一致 -
Access-Control-Allow-Origin在带 cookie 的请求中不能是*,必须动态读取$request->header('origin')并白名单校验后精确回写 - Nginx/Apache 如果也配置了 CORS 头,会造成重复值,浏览器直接拒绝:
The 'Access-Control-Allow-Origin' header contains multiple values - 确认 Web 服务器透传了
Origin头(Nginx 需加proxy_pass_request_headers on;)
真正难调试的点往往不在“怎么写”,而在“谁在它之后又改了一次”。每次加 header 前,先用 curl -I 看真实响应头,比反复改 PHP 更快定位问题源头。



















