简单请求和复杂请求(预检请求 Preflight)的区别是什么?
在浏览器处理 跨域资源共享 (CORS) 时,将请求分为两大类:简单请求 (Simple Requests) 和 复杂请求 (Complex Requests)。
核心区别在于:是否会触发“预检请求”(Preflight Request,即 OPTIONS 请求)。
以下是详细的对比和解释:
1. 简单请求 (Simple Request)
简单请求是指那些不会触发 CORS 预检的请求。浏览器认为这类请求是“安全的”,因为它们类似于传统的 HTML 表单提交(在 CORS 出现之前就已经存在)。
触发条件(必须全部满足)
如果一个请求满足以下 所有 条件,它就是简单请求:
- HTTP 方法 必须是以下三种之一:
GETHEADPOST
- HTTP 头部 (Headers) 不得包含自定义头部,只能包含以下“CORS 安全头部”:
AcceptAccept-LanguageContent-LanguageContent-Type(有限制,见下条)Last-Event-ID(较少见)
- Content-Type 的值仅限于以下三者之一:
text/plainmultipart/form-dataapplication/x-www-form-urlencoded
请求流程
- 浏览器直接发出跨域请求。
- 请求头中携带
Origin字段。 - 服务器返回响应,如果响应头包含正确的
Access-Control-Allow-Origin,浏览器就接收响应;否则报错。
2. 复杂请求 / 需预检的请求 (Preflighted Request)
凡是不满足“简单请求”条件的,都是复杂请求。浏览器担心这类请求可能会对服务器数据产生副作用(如删除、修改),所以要求先询问服务器是否允许。
常见触发场景
只要满足 任意 一个条件,就是复杂请求:
- 使用了非简单的方法:如
PUT,DELETE,CONNECT,OPTIONS,TRACE,PATCH。 - 设置了自定义头部:例如
Authorization(Token),X-Custom-Header等。 - Content-Type 不在允许范围内:最常见的就是
application/json。
请求流程(关键区别)
复杂请求会产生 两次 HTTP 交互:
第一步:预检请求 (Preflight / OPTIONS)
- 浏览器自动先发送一个
OPTIONS方法的请求。 - 询问服务器:“我可以使用
DELETE方法吗?我可以带Authorization头吗?” - 请求头包含:
Access-Control-Request-Method: 实际请求的方法。Access-Control-Request-Headers: 实际请求的自定义头。
第二步:服务器确认
- 服务器检查配置,如果允许,返回状态码
204或200。 - 响应头包含:
Access-Control-Allow-Methods: 允许的方法。Access-Control-Allow-Headers: 允许的头。Access-Control-Max-Age: 预检结果的缓存时间(在此时间内无需再次预检)。
第三步:实际请求
- 只有预检请求成功后,浏览器才会发出真正的业务请求(如
POSTJSON 数据)。
3. 总结对比表
| 特性 | 简单请求 (Simple) | 复杂请求 (Preflighted) |
|---|---|---|
| HTTP 请求次数 | 1次 (直接发送) | 2次 (先 OPTIONS,再发送实际请求) |
| HTTP 方法 | 仅限 GET, POST, HEAD | PUT, DELETE, PATCH 等,或 GET/POST 带有特殊配置 |
| Content-Type | form-data, urlencoded, text/plain | application/json, application/xml 等 |
| 自定义 Header | 不允许 | 允许 (如 Authorization) |
| 典型场景 | 传统的表单提交、加载图片/脚本 | RESTful API 调用 (发送 JSON, 带 Token, 使用 PUT/DELETE) |
| 性能影响 | 较快,低延迟 | 较慢,因为多了一次 OPTIONS 网络往返 |
4. 开发者常遇到的坑
为什么我发送 POST 请求也会触发 OPTIONS 预检?
这是面试或开发中最常见的问题。
- 情况 A (简单请求): 你用 POST 发送表单数据 (
application/x-www-form-urlencoded),不会触发预检。 - 情况 B (复杂请求): 你用 Axios 或 Fetch 发送 JSON 数据 (
application/json),或者你在 Header 里加了Authorization: Bearer token。这会触发预检。
5. 如何优化复杂请求?
由于复杂请求多一次网络往返,会增加延迟。可以通过设置 Access-Control-Max-Age 来优化。
- 服务器在 OPTIONS 响应中设置该字段(单位为秒,例如 86400)。
- 浏览器在有效期内,对完全相同的跨域请求不再发送 OPTIONS,直接发送实际请求。