鸣谢 https://juejin.im/post/5c0a55e76fb9a049ef2665ba
CORS 跨域资源共享
正常发送请求
正常情况下
前端网址为 http://127.0.0.1:58009 向后端http://localhost:8888/ 发送请求
// index.html
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://localhost:8888/')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.send()
// 后端 index.js
const http = require('http')
const PORT = 8888
const server = http.createServer((request, response) => {
response.end("{name: 'quanquan', friend: 'guiling'}")
})
server.listen(PORT, () => {
console.log('服务启动成功, 正在监听: ', PORT);
})
产生跨域错误
可以看到这个错误为 Access-Control-Allow-Origin 错误
所以接下来
我们可以在后端上设置
response.setHeader('Access-Control-Allow-Origin', *)
通配符* 表示允许所有域的请求;如果不想要任何域都可以访问,则可以指定特指域
如 response.setHeader(‘Access-Control-Allow-Origin’, ‘http://127.0.0.1:58009’ )
重新开启后端服务器,就会发现访问成功
当请求为预检请求时
这里,有一个知识点
CORS分为简单请求和预检请求。
什么是简单请求呢?
-
请求为get,post,head的
-
手动设置的头部字段为
Accept Accept-Language Content-Language Content-type DPR Downlink Save-data Viewpoer-Width Width
-
Content-Type为以下三种类型之一
- application/ x-www-form-urlencoded
- multipart/form-data
- text/plain
如果不满足以上条件,则不是简单请求,就为非简单请求
什么是预检请求呢?
进行非简单请求时,浏览器会首先发出类型为OPTIONS的预检请求,请求地址相同。CORS服务端对预检请求作处理,预检请求使用的方法时OPTIONS,用于检查服务器是否支持CORS,以判断实际请求发送是否安全
现在,我们来尝试发送PUT请求,即不是简单请求
// index.html
xhr.open('PUT', 'http://localhost:8888/')
报错
Method PUT is not allowed by Access-Control-Allow-Methods
由此,我们可以想到解决办法
// index.js
response.setHeader('Access-Control-Allow-Method, 'PUT')
… 请求成功
携带自定义请求首部字段token
后端通过以下方法允许前端请求时首部可以携带自定义字段
response.setHeader('Access-Control-Allow-Headers', 'token')
前端怎么携带自定义字段呢?
xhr.setRequestHeader('token', 'quanquanbunengshuo')
通过以上方式,前端请求时Request Header上携带了字段为token,值为quanquanbunengshuo的
如何取到后端返回的token
后端
首先,后端需要设置响应头token
response.setHeader('token','quanquanlin')
前端通过 xhr.getAllResponseHeaders() 获取
console.log(xhr.getAllResponseHeaders())
启动服务器后,我们发现,console.log()只打印出了以下内容
查看响应头
明明返回勒token
此时,在后端设置
response.setHeader('Access-Control-Expose-Headers', 'token')
这样,就可以console出来了
其他
1. 如果想要允许多个指定域名访问呢?
可以把允许的域名添加到数组里,,如下
const allowOrigin = ['http://127.0.0.1:58009', 'https://www.baidu.com']
const {method, headers: {origin, cookie}} = request
if (allowOrigin.includes(origin)) {
response.setHeader('Access-Control-Allow-Origin', origin)
}
2.能否控制预检请求发送两次请求呢?
通过设置Access-Control-Max-Age来控制预检请求的有效期。在指定的时间内再次跨域访问接口,不需要预检请求,单位是秒。
response.setHeader('Access-Control-Max-Age', 5)
3. 预检请求不返回内容
我们的响应结果本来应该是在正式的请求中才需要返回的, 但是我们看下预检请求的返回详情发现
全代码
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>CORS实现跨域</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="">
</head>
<body>
<h3>CORS实现跨域</h3>
<script>
var xhr = new XMLHttpRequest()
xhr.open('PUT', 'http://localhost:8888')
xhr.setRequestHeader('token', 'quanquanbunengshuo')
xhr.withCredentials = true
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
console.log(xhr.getAllResponseHeaders())
}
}
xhr.send()
</script>
</body>
</html>
const http = require('http');
const PORT = 8888;
const allowOrigin = ['http://127.0.0.1:58009', 'https://www.baidu.com']
// 创建一个 http 服务
const server = http.createServer((request, response) => {
const {method, headers: {origin, cookie}} = request
if (allowOrigin.includes(origin)) {
response.setHeader('Access-Control-Allow-Origin', '*')
}
response.setHeader('Access-Control-Allow-Methods', 'PUT')
response.setHeader('Access-Control-Allow-Headers', 'token')
response.setHeader('Access-Control-Max-Age', 5)
// 允许前端请求携带cookie
response.setHeader('Access-Control-Allow-Credentials', true)
response.setHeader('token', 'quanquanlin')
response.setHeader('Access-Control-Expose-Headers', 'token')
if (method === 'OPTIONS') {
response.writeHead(204);
response.end('')
} else if (!cookie) {
response.setHeader('Set-Cookie', 'quanquanlin=fe')
}
response.end("{name: 'quanquan', friend: 'guiling'}")
});
// 启动服务, 监听端口
server.listen(PORT, () => {
console.log('服务启动成功, 正在监听: ', PORT);
});