浏览器发两次请求是因为非简单请求触发了OPTIONS预检机制。第一次是无数据的OPTIONS请求,询问后端是否允许后续带特定头或方法的实际请求;只有预检通过,第二次才发送真实业务数据。

为什么浏览器发了两次请求,第二次才带数据?
因为前端用 fetch 或 axios 发送非简单请求(比如带 Content-Type: application/json、自定义 header、PUT/DELETE 方法)时,浏览器会自动先发一个 OPTIONS 预检请求。它不带业务数据,只问后端:“我接下来想发个带认证的 JSON 请求,你允许吗?”——如果后端没正确响应这个 OPTIONS,后续请求根本不会发出。
Flask/FastAPI/Django里怎么让OPTIONS不报405?
常见错误是只写了 @app.route('/api/data', methods=['POST']),但没声明 OPTIONS。浏览器一预检就 405 Method Not Allowed。
- Flask:显式加
methods=['GET', 'POST', 'OPTIONS'],并在函数里对request.method == 'OPTIONS'返回空响应 + 正确 CORS 头 - FastAPI:用
@app.options('/api/data')单独写一个路由,或直接在主路由里用methods=['POST', 'OPTIONS']并检查request.method - Django:如果是函数视图,确保
if request.method == 'OPTIONS': return HttpResponse(status=200, headers={...});用 DRF 的话,别只依赖CORS_ALLOW_ALL_ORIGINS=True,它默认不处理自定义 header 的预检
Access-Control-Allow-Origin设成*为什么还报错?
因为一旦你返回了 Access-Control-Allow-Credentials: true(比如带 cookie 登录),浏览器就禁止 Access-Control-Allow-Origin: *。必须精确匹配前端 origin,比如 https://my-fe.com。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 后端不能硬编码单个域名——要从
Origin请求头里读取并回传(需校验白名单,防开放重放) -
Access-Control-Allow-Headers必须包含前端实际发送的字段,比如Authorization、X-Request-ID,漏一个,预检就失败 -
Access-Control-Allow-Methods要写全,比如GET, POST, PUT, DELETE, OPTIONS,少写OPTIONS也会导致预检被拒
本地开发时localhost:3000调localhost:8000为啥总跨域?
协议、域名、端口任一不同就算跨域。http://localhost:3000 和 http://localhost:8000 端口不同,就是跨域。别信“都是 localhost 就没事”这种直觉。
- 开发阶段最稳的方式:前端用
proxy(如 vite.config.ts 的server.proxy)把 /api 代理到后端,让浏览器认为是同源请求 - 后端临时开启宽松 CORS 可以,但别在
Access-Control-Allow-Origin里写*同时又开credentials,否则 Chrome 直接静默失败,控制台可能只显示 “CORS error”,连具体哪条 header 不符都不提示 - 注意:某些反向代理(如 Nginx)会吞掉
OPTIONS请求或改写 header,要在 proxy 配置里显式透传Access-Control-*头
预检不是玄学,本质是浏览器强制的安全协商机制;真正难的不是加几行 header,而是理解哪些 header 是“必须由后端显式声明”的,以及它们之间的约束关系——比如 credentials 和 origin 互斥,method 列表和实际请求方法必须严格对应,漏一条,整个链路就断在第一步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










