简单请求与非简单请求的本质区别在于浏览器是否自动发送options预检请求:简单请求需同时满足方法限于get/head/post、请求头仅含安全字段、content-type为三种白名单类型且无自定义头;否则即为非简单请求,必先发options“问路”。

JS 中的简单请求和非简单请求,本质区别在于浏览器是否自动加发一次 OPTIONS 预检请求。这个行为由 CORS 规范强制约定,不是前端能绕开的,也不取决于你用 fetch 还是 axios —— 只要满足条件,浏览器就悄悄多发一次 OPTIONS。
哪些请求算“简单请求”?
必须同时满足以下全部条件:
- 请求方法仅限 GET、HEAD 或 POST
- 请求头只包含“安全字段”:如
Accept、Accept-Language、Content-Language、Last-Event-ID,以及Content-Type(但仅限以下三种值):application/x-www-form-urlencodedmultipart/form-datatext/plain
- 没有设置自定义请求头(比如
Authorization、X-Request-ID、token等) - 没监听
XMLHttpRequest.upload事件,也没使用ReadableStream
什么情况会触发 OPTIONS 预检?
只要不满足上面任一条件,就是非简单请求,浏览器会在真正发请求前,先发一个 OPTIONS 请求“问路”:
- 用了
PUT、DELETE、PATCH、TRACE等方法 -
Content-Type是application/json、application/xml、text/csv等非白名单类型 - 手动设置了任何自定义请求头,例如:
headers: { 'X-Token': 'abc' }或Authorization: Bearer xxx - 哪怕只是 POST +
application/json,也立刻触发预检
预检请求到底在问什么?
OPTIONS 请求本身不传业务数据,它只带几个关键头,向服务器确认权限:
-
Origin:告诉服务器“我是从哪儿来的” -
Access-Control-Request-Method:比如PUT,说明接下来想用这个方法 -
Access-Control-Request-Headers:比如Content-Type, X-User-ID,列出所有将要带的非标准头
服务器必须在响应中明确允许这些项(如 Access-Control-Allow-Methods: PUT,DELETE、Access-Control-Allow-Headers: Content-Type,X-User-ID),否则浏览器直接拦截后续真实请求。
前后端配合的关键点
后端不能只写 Access-Control-Allow-Origin: * 就完事 —— 预检通过需要更细粒度的响应头:
- 对 OPTIONS 请求,必须返回
204 No Content或200 OK,且响应头中需包含:-
Access-Control-Allow-Origin(若带凭证,不能为*) -
Access-Control-Allow-Methods(列出允许的方法) -
Access-Control-Allow-Headers(列出允许的请求头) - 如需带 cookie 或认证信息,还要加
Access-Control-Allow-Credentials: true
-
- 前端可尽量“退一步”规避预检:比如 POST 提交 JSON 数据时,改用
application/x-www-form-urlencoded格式 + 拆成字符串传参(前提是接口支持)











