跨域问题的多种解决方案
1. jsonp
原理:script标签不受同源策略的影响,把链接挂在script标签上,通过回调函数传递数据
优点:兼容性好,前后端分离
缺点:仅支持get请求,安全性较差,容易引发xss攻击
/* server.js */
const express = require('express');
const app = express();
app.get('/say', (req, res) => {
let { wd, cb } = req.query;
console.log('客户端:' + wd);//客户端:Hello
res.end(`${cb}('服务端:Hi')`)
})
app.listen(3333, () => {
console.log("Server start http://localhost:3333");
})
<script>
function jsonp({ url, params, cb }) {
/* 返回一个Promise */
return new Promise((resolve) => {
/* 创建script */
let script = document.createElement('script');
/* 全局cb函数 */
window[cb] = function (data) {
resolve(data);/* 执行返回的数据 */
document.body.remove(script);/* 执行完毕删除标签 */
}
/* 转换url */
let arr = [];
params = { ...params, cb };
for (let key in params)
arr.push(`${key}=${params[key]}`);
//http://localhost:3333/say?wd=Hello&cb=show
script.src = `${url}?${arr.join('&')}`;
document.body.appendChild(script);
})
}
jsonp({
url: 'http://localhost:3333/say',
params: { wd: 'Hello' },
cb: 'show'
}).then(data => console.log(data));//服务端:Hi
</script>
2. CORS
原理:通过在服务端添加白名单,放宽对请求源的限制,从而实现跨域
优点:可以发任意请求
缺点:上是复杂请求的时候得先做一个预检,再发真实的请求,发了两次请求会有性能上的损耗。
3333端口下的indexhtml发出ajax请求
<!-- html在3333端口服务器上 -->
<script>
let xhr = new XMLHttpRequest;
/* 设置cookie 需要Access-Control-Allow-Credential设置 */
document.cookie = "name=aeipyuan";
xhr.withCredentials = true;
/* 请求4444端口数据 */
xhr.open('put', 'http://localhost:4444/getData', true);
/* 需要设置Access-Control-Allow-Headers */
xhr.setRequestHeader('name', 'A');
xhr.send();
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
console.log(xhr.response);//4444已收到
// 需要设置Access-Control-Expose-Headers
console.log(xhr.getResponseHeader('name'));//mike
}
}
}
</script>
4444端口服务器对数据进行处理
const express = require('express');
const app = express();
/* 设置白名单 */
let writeList = ['http://localhost:3333'];
app.use((req, res, next) => {
/* 获取请求源 */
let { origin } = req.headers;
/* 判断请求源 */
if (writeList.includes(origin)) {
/* 允许origin访问 */
res.setHeader('Access-Control-Allow-Origin', origin)
/* 允许哪个头 */
res.setHeader('Access-Control-Allow-Headers', 'name')
/* 允许哪个方法 */
res.setHeader('Access-Control-Allow-Methods', 'PUT')
/* 允许携带cookie */
res.setHeader('Access-Control-Allow-Credentials', true)
/* 预检的存活时间 */
res.setHeader('Access-Control-Allow-Max-Age', 6)
/* 允许前端获取哪个头 */
res.setHeader('Access-Control-Expose-Headers', 'name')
/* OPTIONS请求不做处理 */
if (req.method === 'OPTIONS') {
res.end();
}
}
next();
})
app.put('/getData', (req, res) => {
console.log(req.headers);
res.setHeader('name', 'B');
res.send('4444已收到');
})
app.listen(4444, () => {
console.log("Server start http://localhost:4444");
})
3. postMessage实现跨域
原理:将另一个域的网页放到iframe中,利用postMessage进行传值
3333端口下的a.html
<div>AAAAAAA</div>
<iframe id="frame" src="http://localhost:4444/b.html" frameborder="0" onload="load()"></iframe>
<script>
function load() {
frame.contentWindow.postMessage(
'Hello', 'http://localhost:4444/b.html'
)
}
window.onmessage = function (e) {
console.log('B说:' + e.data);//B说:Hi
}
</script>
4444端口下的b.html
<div>BBBBBB</div>
<script>
window.onmessage = function (e) {
console.log('A说:' + e.data);//A说:Hello
e.source.postMessage('Hi', e.origin);//给A发送消息
}
</script>
4. window.name传值
原理:先用iframe的window.name存储跨域页面要传入的数据,然后将iframe的src属性改变为同源src,实现获取name存储的值
举例:
A,B页面在3333端口下,C页面在4444端口下,目标是实现a页面获取c页面数据
第一步,A页面用iframe标签引入C页面
第二步,C页面设置window.name=数据
第三步,将iframe的src由C页面切换为B页面(同源)
第四步,获取iframe页面的window.name属性
<!-- a.html -->
<iframe src="http://localhost:8082/c.html" frameborder="10" onload="load()" id="frame"></iframe>
<script>
let first = true;
function load() {
if (first) {
let frame = document.getElementById('frame');
frame.src = "http://localhost:8081/b.html";//切换src
first = false;
} else {
console.log(frame.contentWindow.name)
}
}
</script>
<!-- c.html -->
<script>
window.onload = function () {
window.name = "传给A的数据"
}
</script>
5. hash传值
原理:和window.name相似,A使用iframe引入C并给C传hash值,C使用iframe引入B并给B传hash值,B和A同源,所以把hash值赋给A,A监听到hash变化输出hash值
<!-- a.html -->
<iframe id="frame" src="http://localhost:4444/c.html#A2C" frameborder="0"></iframe>
<script>
window.onhashchange = function () {
console.log('C传入数据:' + location.hash)//C传入数据:#C2B2A
}
</script>
<!-- b.html -->
<script>
window.parent.parent.location.hash = location.hash;/* 将hash传给A */
</script>
<!-- c.html -->
<script>
console.log('A传入的数据:' + location.hash);//A传入的数据:#A2C
/* 创建iframe */
let iframe = document.createElement('iframe');
iframe.src = "http://localhost:3333/b.html#C2B2A";
document.body.appendChild(iframe);
</script>
6. Websocket
原理:Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了
开启服务
/* server.js */
let WebSocket = require('ws');
/* 创建服务 */
let wss = new WebSocket.Server({ port: 3333 });
wss.on('connection', ws => {
ws.on('message', e => {
console.log(e);//前端数据
ws.send('后台数据');
})
})
传送数据
<script>
let socket = new WebSocket('ws://localhost:3333');
socket.onopen = function () {
socket.send('前端数据');
}
socket.onmessage = ({ data }) => {
console.log(data);//后台数据
}
</script>
7. domain实现跨域
不同的页面可能放在不同的服务器上,这些服务器域名不同,但是拥有相同的上级域名,比如id.qq.com、www.qq.com、user.qzone.qq.com,它们都有公共的上级域名qq.com ,设置页面documen.domain为上级域名即可实现跨域
<!-- http://a.aeipyuan.cn:3333/a.html -->
<div>AAAAA</div>
<iframe id="frame" src="http://b.aeipyuan.cn:4444/b.html" frameborder="0" onload="load()"></iframe>
<script>
document.domain = "aeipyuan.cn";
function load() {
console.log('b页面数据:' + frame.contentWindow.a);//b页面数据:100
}
</script>
<!-- http://b.aeipyuan.cn:4444/b.html -->
<div>BBBBB</div>
<script>
document.domain = "aeipyuan.cn";
window.a = 100;
</script>
8. nginx实现
在conf文件配置以下参数,了解较浅,日后补充
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
9. webpack配置实现跨域
//方式1 webpack.config.js
devServer: {
port: 8081,
contentBase: './build',
hot: true,
proxy: {
'/api': {
target: 'http://localhost:8888',//目标域
pathRewrite: { '/api': '' }/* 路径重写 */
}
}
}
//方式2 server.js 直接在8888端口访问webpack打包文件
let express = require('express');
let app = express();
/* webpack */
let webpack = require('webpack');
let config = require('../webpack.config.js');
let compiler = webpack(config);
//中间件
let middle = require('webpack-dev-middleware');
app.use(middle(compiler));
/* 请求 */
app.get('/user', (req, res) => {
res.json({ name: "aeipyuan" })
})
app.listen(8888);