浏览器会在以下情况下自动发送预检请求(Preflight Request):
简单请求 vs 复杂请求
简单请求(不触发预检)
必须同时满足以下所有条件:
1. HTTP 方法限制
只能是以下三种方法之一:
GET
HEAD
POST
2. 请求头限制
只能包含以下安全头部:
Accept
Accept-Language
Content-Language
Content-Type
(但有值的限制)Range
(简单范围请求)
3. Content-Type 限制
如果使用 POST 且包含 Content-Type,只能是:
text/plain
multipart/form-data
application/x-www-form-urlencoded
4. 其他限制
- 不能有自定义请求头
- 不能使用
XMLHttpRequestUpload
对象注册事件监听器 - 不能使用
ReadableStream
对象
触发预检请求的条件(复杂请求)
满足以下任意一个条件就会触发预检:
1. HTTP 方法
javascript
// 这些方法会触发预检
fetch(url, { method: 'PUT' });
fetch(url, { method: 'DELETE' });
fetch(url, { method: 'PATCH' });
fetch(url, { method: 'CONNECT' });
2. 自定义请求头
javascript
// 任何自定义头部都会触发预检
fetch(url, {
headers: {
'X-Custom-Header': 'value', // 自定义头部
'Authorization': 'Bearer token', // 认证头部
'X-Requested-With': 'XMLHttpRequest'
}
});
3. Content-Type 不在简单值范围内
javascript
// 这些 Content-Type 会触发预检
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json' // ❌ 触发预检
}
});
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/xml' // ❌ 触发预检
}
});
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'text/html' // ❌ 触发预检
}
});
4. 使用凭据
javascript
// 设置凭据会触发预检
fetch(url, {
credentials: 'include' // 包含 cookies
});
实际示例对比
✅ 简单请求(不触发预检)
javascript
// GET 请求
fetch('http://127.0.0.1:3333/list');
// 简单 POST
fetch('http://127.0.0.1:3333/list', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'name=john&age=30'
});
// 纯文本 POST
fetch('http://127.0.0.1:3333/list', {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: 'hello world'
});
❌ 复杂请求(触发预检)
javascript
// JSON 数据
fetch('http://127.0.0.1:3333/list', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // 触发预检
},
body: JSON.stringify({name: 'john'})
});
// 自定义头部
fetch('http://127.0.0.1:3333/list', {
headers: {
'Authorization': 'Bearer token' // 触发预检
}
});
// PUT 方法
fetch('http://127.0.0.1:3333/list', {
method: 'PUT', // 触发预检
body: 'data'
});
预检请求的内容
当触发预检时,浏览器会发送:
http
OPTIONS /list HTTP/1.1
Host: 127.0.0.1:3333
Origin: http://127.0.0.1:5500
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
服务器需要响应:
http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://127.0.0.1:5500
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
避免预检的技巧
如果想避免预检请求:
- 使用 GET/HEAD/POST 方法
- 避免自定义头部
- POST 时使用
application/x-www-form-urlencoded
或text/plain
- 不使用认证相关头部
但在现代 Web 开发中,JSON API 很常见,所以预检请求是不可避免的,正确配置 CORS 才是解决方案。