这篇文章主要讲解在开发过程中,前后端是如何解决跨域问题的,关于跨源资源共享的理论知识可以通过阅读MDN 跨源资源共享(CORS)的文章了解。
解决跨域的手段有很多,这里主要是通过后端 nodejs
来做示例。
准备工作
我们知道同源策略约定协议
、域名
、端口
三者相同才能访问到资源,这里通过nodejs来创造一个不同端口的跨域请求环境。
api接口服务
- api.js
// api.js
const handleRequest = (req, res) => {
res.end(JSON.stringify({result:"success"})
)}
require("http").createServer(handleRequest).listen(3000)
前端服务
前端服务index.html
和server.js
- server.js
- index.html
// server.js
const fs = require("fs")
const path = require("path")
const handleRequest=(req,res)=>{
const indexpath = path.resolve(__dirname, "./index.html");
fs.createReadStream(indexpath).pipe(res)
};
require("http").createServer(handleRequest).listen(8080)
//index.html
<html><body>
<script>
const request = new XMLHttpRequest();
request.responseType="json";
request.open("GET","http://127.0.0.1:3000",true);
request.onload = ()=>{
console.log(request.response);
}
request.send();
</script>
</body></html>
启动服务
在nodejs
环境下执行node api
和node server
,打开浏览器,输入http://127.0.0.1:8080
,在控制台可以查看到报错信息:
Access to XMLHttpRequest at 'http://127.0.0.1:3000/' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Access-Control-Allow-Origin
解决跨域第一步是响应头设置Access-Control-Allow-Origin
:
Access-Control-Allow-Origin: <origin> | *
origin
参数的值指定了允许访问该资源的外域 URI,通配符 *
表示允许所有,修改api.js
如下:
const handleRequest = (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*")
res.end(JSON.stringify({result:"success"}))
}
重新启动node api
,刷新8080
的地址就可以看到跨域访问的资源了
Access-Control-Allow-Methods
项目中使用RESTful API
的话,除了get
和post
请求,一定还会有delete
和put
等请求,我们修改index.html
,换成delete
请求:
request.open("DELETE","http://127.0.0.1:3000",true);
打开浏览器后会发现,控制台又出现了跨域的报错信息:
Access to XMLHttpRequest at 'http://127.0.0.1:3000/' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response.
修改api.js
,添加上Access-Control-Allow-Methods
属性可以解决这个问题:
res.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS")
Access-Control-Max-Age
在浏览器控制台Network
中,可以看到每次发送的XHR
请求都带上一个options
请求,这是因为浏览器必须首先使用 OPTIONS
方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求,我们可以给响应头添加Access-Control-Max-Age
,避免每次都发送预检请求,限制options发送频率:
res.setHeader("Access-Control-Max-Age", 86400)
Access-Control-Max-Age
表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。
请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。
Access-Control-Allow-Headers
在前后端的通信中,我们经常会在请求头加上tokan
信息发送给服务端,修改index.html
如下:
request.open("POST","http://127.0.0.1:3000/get",true);
request.responseType="json";
+ request.setRequestHeader("Authorization","token");
request.onload = ()=>{
console.log(request.response);
}
request.send();
打开浏览器控制台,我们会看到新的跨域报错信息:
Access to XMLHttpRequest at 'http://127.0.0.1:3000/get' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.
修改api.js
,添加上Access-Control-Allow-Headers
,指明实际请求中允许携带的首部字段:
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization")
Access-Control-Allow-Credentials
我们在使用一些表单或者上传组件库的时候,经常可以看到withCredentials
这个属性的设置,因为在跨域的情况下设置的cookies
是不发送到服务器的,只有当后端响应头Access-Control-Allow-Credentials
为true
时,才能获取到cookies
信息。
// index.html
request.withCredentials = true;
document.cookie="name=chenwl";
res.setHeader("Access-Control-Allow-Credentials",true)
不过即便设置了Access-Control-Allow-Credentials
,这里后端还是无法拿到cookies
信息,因为之前Access-Control-Allow-Origin
这里设置成了"*",这里要求 必须指定明确的、与请求网页一致的域名,修改Access-Control-Allow-Origin
如下:
res.setHeader("Access-Control-Allow-Origin", req.headers.origin)
通常情况下,我们还会为Access-Control-Allow-Origin
设置白名单:
const corsWhitelist = [
"https://domain1.example",
"https://domain2.example",
"https://domain3.example",
]
if (corsWhitelist.indexOf(req.headers.origin) !== -1) {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin)
}
完整代码
api.js
const handleRequest = (req, res) => {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin)
res.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS")
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization")
res.setHeader("Access-Control-Max-Age", 86400)
res.setHeader("Access-Control-Allow-Credentials", true)
res.end(JSON.stringify({ cookie: req.headers.cookie }))
}
require("http").createServer(handleRequest).listen(3000)
index.html
<html><body>
<script>
let request = new XMLHttpRequest();
request.open("POST","http://127.0.0.1:3000/get",true);
request.responseType="json";
request.setRequestHeader("Authorization","tokena");
request.setRequestHeader("Content-Type","application/json;charsrt=utf-8");
request.withCredentials = true;
document.cookie="name=chenwl";
request.onload = ()=>{console.log(request.response)}
request.send();
</script>
</body></html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。