简单请求为何无需预检
理解 CORS 中简单请求跳过预检的原因
问题
在 CORS 机制中,为什么简单请求不需要发送预检请求(OPTIONS),而复杂请求需要?
解答
什么是简单请求
满足以下所有条件的请求被视为简单请求:
- 方法限制:GET、HEAD、POST 之一
- 请求头限制:只能包含以下头部
- Accept
- Accept-Language
- Content-Language
- Content-Type(仅限下面三种)
- Content-Type 限制:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
不需要预检的原因
核心原因:历史兼容性
在 CORS 出现之前,浏览器已经允许通过 <form> 表单和 <img> 标签发起跨域请求:
<!-- 这些跨域请求在 CORS 之前就被允许 -->
<form action="https://other-site.com/api" method="POST">
<input type="text" name="data">
<button type="submit">提交</button>
</form>
<img src="https://other-site.com/image.png">
这些传统方式能发起的请求,恰好就是”简单请求”的范围。服务器早已能处理这类请求,不会因为它们产生意外的副作用。
预检的目的是保护服务器
// 复杂请求示例 - 需要预检
fetch('https://api.example.com/data', {
method: 'DELETE', // 非简单方法
headers: {
'Content-Type': 'application/json', // 非简单 Content-Type
'X-Custom-Header': 'value' // 自定义头
}
});
预检请求让服务器有机会在实际请求到达前决定是否接受。对于可能修改服务器数据的复杂请求,这层保护很重要。
请求流程对比
// 简单请求 - 直接发送
fetch('https://api.example.com/users', {
method: 'GET'
});
// 浏览器直接发送 GET 请求,检查响应头决定是否暴露给 JS
// 复杂请求 - 先预检
fetch('https://api.example.com/users', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'test' })
});
// 浏览器先发 OPTIONS 请求
// 服务器返回允许的方法和头部
// 预检通过后才发送实际 PUT 请求
预检请求示例
# 预检请求
OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://mysite.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
# 预检响应
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://mysite.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
关键点
- 简单请求的范围等同于 CORS 之前浏览器就能发起的跨域请求
- 预检机制是为了保护服务器免受新型请求的意外影响
- 简单请求仍会检查响应头,只是跳过了预检步骤
Access-Control-Max-Age可缓存预检结果,减少 OPTIONS 请求次数- 即使是简单请求,服务器仍需返回正确的 CORS 头部,否则响应不会暴露给 JavaScript
目录