underflowed
duanyuhao's space








duanyuhao's space








在 CORS(跨源资源共享)机制中,某些请求不会触发预检请求(Preflight Request),这类请求被称为简单请求。
简单请求的设计基于这样一个事实:HTML 4.0 中的 <form> 元素早于跨站 XMLHttpRequest 和 fetch(),它可以向任何源提交简单请求。因此,任何编写服务器的开发者都必须已经在防护跨站请求伪造攻击(CSRF)。
在这个假设下,服务器不必通过响应预检请求来选择性接收任何看起来像表单提交的请求,因为 CSRF 的威胁并不比表单提交更严重。
若请求同时满足以下所有条件,则该请求可视为简单请求:
请求必须使用以下方法之一:
| 方法 | 说明 |
GET |
| 获取资源 |
HEAD | 获取资源元信息 |
POST | 提交数据 |
除了被用户代理自动设置的标头字段(如 Connection、User-Agent),只允许手动设置以下 CORS 安全标头:
AcceptAccept-LanguageContent-LanguageContent-Type(需要注意额外限制)Range(仅允许简单的范围值,如 bytes=256- 或 bytes=127-255)Content-Type 标头的值仅限于以下三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencodedContent-Type 是 application/json、text/xml 等其他类型,将触发预检请求!如果请求是使用 XMLHttpRequest 对象发出的:
XMLHttpRequest.upload 对象属性上没有注册任何事件监听器xhr.upload.addEventListener() 来监听上传请求请求中没有使用 ReadableStream 对象。
const fetchPromise = fetch("https://bar.other/resources/public-data/");
fetchPromise
.then((response) => response.json())
.then((data) => {
console.log(data);
});GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 ...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.exampleOrigin 字段表明该请求来源于 https://foo.example。这个标头在所有访问控制请求中总是被发送。HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]服务器通过 Access-Control-Allow-Origin 标头来控制跨源访问:
Access-Control-Allow-Origin: *表示该资源可以被任意外源访问。
Access-Control-Allow-Origin: https://foo.example限制只有 https://foo.example 域可以通过跨源访问该资源。
Access-Control-Allow-Origin 的值,而不能使用通配符 *。如果服务端指定了具体的单个源(可能会根据请求的来源动态改变),则响应标头中的 Vary 字段必须包含 Origin:
Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin这将告诉客户端:服务器对不同的 Origin 返回不同的内容。
| 特性 | 简单请求 | 预检请求 |
| 是否发送 OPTIONS | ❌ 否 | ✅ 是 |
| HTTP 方法 | GET、HEAD、POST | PUT、DELETE、PATCH 等 |
| Content-Type | text/plain、multipart/form-data、application/x-www-form-urlencoded | application/json、text/xml 等 |
| 自定义请求头 | ❌ 不允许 | ✅ 允许 |
| 请求次数 | 1 次 | 2 次(OPTIONS + 实际请求) |
WebKit Nightly 和 Safari Technology Preview 为 Accept、Accept-Language 和 Content-Language 标头字段的值添加了额外的限制。如果这些标头字段的值是「非标准」的,WebKit/Safari 就不会将这些请求视为简单请求。
其他浏览器并不支持这些额外的限制,因为它们不属于规范的一部分。
当可能时,设计 API 以支持简单请求,可以减少一次网络往返:
// ✅ 简单请求 - 使用 form-urlencoded
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'name=value&foo=bar'
});
// ❌ 触发预检 - 使用 JSON
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'value' })
});// ❌ 触发预检 - 自定义头
fetch('/api/data', {
headers: {
'X-Custom-Header': 'value'
}
});
// ✅ 简单请求 - 无自定义头
fetch('/api/data');// Node.js Express 示例
app.use((req, res, next) => {
// 设置允许的源
res.header('Access-Control-Allow-Origin', 'https://your-domain.com');
// 动态源时需要 Vary 头
res.header('Vary', 'Origin');
next();
});