- 并不是所有情况的跨域都可以设置
Access-Control-Allow-Origin
头 - 我们还是通过例子来说明
自定义的头信息
1 ) 准备程序
-
server1.js
const http = require('http'); const fs = require('fs'); http.createServer((req, res) => { console.log('req come: ', req.url); const html = fs.readFileSync('test.html', 'utf8'); // 默认是下面这个writeHead设置,不写也可以 res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); }).listen(8000, () => { console.log('server is running on port 8000'); });
-
server2.js
const http = require('http'); http.createServer((req, res) => { console.log('req come: ', req.url); // 在writeHead时候添加跨域支持 res.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); res.end('req come: ' + req.url); }).listen(8001, () => { console.log('server is running on port 8001'); });
-
test.html
<script> fetch('http://localhost:8001', { method: 'POST', headers: { 'X-Test-Cors': '123' } }); </script>
2 ) 启动两个程序
- $
node server1.js
- $
node server2.js
- 打开浏览器访问:http://localhost:8000/
3 ) 出现问题
- 检查页面节点,已经正常加载
- 浏览器debug后,发现报错了
- 此时就会报错
Access to fetch at 'http://localhost:8001/' from origin 'http://localhost:8000' has been blocked by CORS policy: Request header field x-test-cors is not allowed by Access-Control-Allow-Headers in preflight response.
4 ) 问题分析
- 4.1 ) 上面报错信息是说,我们自己加上这个头信息
X-Test-Cors
在跨域请求中是不被允许的 - 注意,一定要注意是在跨域请求中,如果是同域请求是被允许的,这就属于CORS预请求所研究的范畴了
- 具体的请求头限制可以参考:https://fetch.spec.whatwg.org/#cors-safelisted-request-header
- 4.2 ) 而在跨域的时候默认允许的方法只有三种:GET、POST、HEAD这三种
- 其他方法都是不被允许的,浏览器会用预请求的方式来验证
- 4.3 ) 同时允许的Content-Type也有限制,只允许:text/plain、multipart/form-data、application/x-www-form-urlencoded这三种
- 其他的Content-Type同样也需要验证
- 4.4 ) 其他限制
- 4.41 ) XMLHttpRequestUpload对象均没有注册任何事件监听器
- 4.42 ) 请求中没有使用ReadableStream对象
- 上面这两个很少会被用到
5 ) 关于预请求与解决方案
- 浏览器是根据什么来判断请求是否被允许呢?这就是根据header来判断
- 如果程序需要添加头信息,服务端在响应的时候需要返回一个头告诉浏览器这个头是被允许的
- 所以这时候,我们只需要添加
res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'X-Test-Cors' // 新添加这行 });
- 添加之后,就可以正确接收了, 但是我们可以发现, 比刚才多了一个OPTIONS请求, 这个就是预请求
- 通过OPTIONS请求来获得服务端允许的认可, 然后再次发送POST请求
- 这就是浏览器对于跨域请求预请求的一个操作
- 注意,如果之前test.html中的fetch函数用的是GET,同样会发送两个请求,一个是OPTIONS,一个是GET
- 什么时候需要预请求呢?一般自定义的请求需要进行预请求来进行验证,此处不再展开
- 注意,OPTIONS不是POST请求的专属,一般的GET,POST是没有必要进行OPTIONS请求的
- 同样,也不是使用fetch这个api的原因,就是单纯的HTTP规则
- 注意,如果使用没有定义的方法,如PUT来请求,如下
<script> fetch('http://localhost:8001', { method: 'PUT' }); </script>
- 会出现问题
Access to fetch at 'http://localhost:8001/' from origin 'http://localhost:8000' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
- 这个问题说明当前不被允许,所以,这个问题需要解决
- 我们想要实现RESTful API的时候,需要添加语义化的请求方法,如:PUT,DELETE,PATCH等
- 这个时候,我们需要添加对各类方法的支持,注意GET,POST,OPTIONS不必添加,默认支持
res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'X-Test-Cors', 'Access-Control-Allow-Methods': 'PUT, DELETE, PATCH' // 新添加这行,一般GET,POST,OPTIONS不必添加,默认支持 });
- 添加支持后,同样,我们可以看到有两次请求,一个是OPTIONS请求,一个是新添加的请求方法
- 注意,如果你用其他未支持的方法,比如自定义的字符来作为请求方法的话,如定义一个字符串或字符'X'作为请求方法
- 那么这时候即使服务端添加了对'X'方法的支持,结果仍只有一个OPTIONS预请求,最终依然无法通过
- 因为这已经超越规则了,不被HTTP协议所允许
- 浏览器通过这种机制来保证跨域访问的安全性
- 关于预请求,还有一个常用的设置
Access-Control-Max-Age
, 通过这个设置,浏览器可以在设置的时间内不会再次发送预请求res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'X-Test-Cors', 'Access-Control-Allow-Methods': 'PUT, DELETE, PATCH', 'Access-Control-Max-Age': 1000 // 新添加这行, 1000s内不会再次发送预请求 });