前言
在前后端交互中,经常会碰到请求跨域,那什么是跨域呢,以及如何解决跨域,本文将进行探讨分享。
什么是跨域
因为浏览器的同源策略,非同源的情况下即会产生跨域现象。会导致跨域的场景有 当 URL 的协议、主域名、子域名、端口中任意一个不相同的时候,就会产生跨域。
跨域其实请求发送出去了,并且服务器也进行响应了,但是浏览器拦截了返回结果。
跨域的解决方案
(1)jsonp
原理:利用 script 标签没有跨域限制的特性,向服务器发送请求。它只支持 GET 方法。
实现流程:
- 封装一个函数名为 jsonp 的方法,接收 url、params、callback 三个参数,返回一个 promise 。
- 内部实现,首先在 window 上创建一个跟传入的回调函数名一样的函数,用于接收服务器返回的数据,并且在接收完后,把动态创建的 script 删除,把 window 上挂载的此方法设为 null
- 创建一个 script 标签,把传入的url、参数和回调函数,拼接成 script 的 src
- 最后服务器处理,并返回一个以传入的回调函数名的方法,并把需要传递给客户端的数据,作为函数的参数。
客户端实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style></style>
</head>
<body>
<button onclick="send()">发送jsonp</button>
<script>
function jsonp({ url, params, callback = 'show' }) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
// 创建回调函数
window[callback] = function (res) {
resolve(res)
document.body.removeChild(script)
window[callback] = null
}
// 处理参数
params = {
callback,
...params,
}
let arr = []
for (const key in params) {
arr.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arr.join('&')}`
document.body.appendChild(script)
})
}
function send() {
jsonp({
url: 'http://localhost:2000/say',
params: {
id: 1,
},
}).then((res) => {
console.log(res) // {id: 1, name: "sky"}
})
}
</script>
</body>
</html>
服务端实现
const express = require('express')
const app = express()
app.get('/say', (req, res) => {
const { callback, id } = req.query
if (id === '1') {
let obj = { id: 1, name: 'sky' }
res.end(`${callback}(${JSON.stringify(obj)})`)
} else if (id === '2') {
let obj = { id: 2, name: 'jeeny' }
res.end(`${callback}(${JSON.stringify(obj)})`)
} else {
res.end(`${callback}('不存在此id')`)
}
})
app.listen(2000)
(2)CORS(跨源资源共享)
实现 CORS 的关键是后端,只要后端配置就能够实现跨域。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS,该属性表示哪些域名可以访问。
(3) postMessage
它可以解决以下2种情况:
- 页面与嵌套的 iframe 传递消息
父级页面 01.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>我是01.html</h2>
<button onclick="handleMessage()">发送消息</button>
<iframe
src="http://localhost:5000/02.html"
style="width: 300px; height: 300px"
></iframe>
<script>
// iframe 地址不能用系统文件地址格式,可以开启一个服务
function handleMessage() {
const iframe = document.getElementsByTagName('iframe')[0]
iframe.contentWindow.postMessage(
'我是发送到02页面的内容',
'http://localhost:5000/02.html'
)
}
window.addEventListener('message', (res) => {
// 接收其他页面传过来的内容
console.log(res)
})
</script>
</body>
</html>
子页面 02.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>我是02.html</h2>
<button onclick="handleMessage()">发送消息</button>
<script>
function handleMessage() {
window.parent.postMessage('返回给01页面的消息')
}
window.addEventListener('message', (res) => {
// 接收其他页面传过来的内容
console.log(res)
})
</script>
</body>
</html>
- 页面与通过 window.open 打开的页面通信,注意:父页面向子页面发送消息时,需子页面已经被 window.open 打开,或者打开的同时,设置一个异步,延迟100ms左右再发送消息
父页面01.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>我是01.html</h2>
<button onclick="handleMessage()">发送消息</button>
<script>
function handleMessage() {
const iframe = window.open('http://localhost:5000/02.html')
setTimeout(() => {
iframe.postMessage('我是发送到02页面的内容')
}, 100)
}
window.addEventListener('message', (res) => {
// 接收其他页面传过来的内容
console.log(res)
})
</script>
</body>
</html>
子页面 02.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>我是02.html</h2>
<button onclick="handleMessage()">发送消息</button>
<script>
function handleMessage() {
window.opener.postMessage('返回给01页面的消息')
}
window.addEventListener('message', (res) => {
// 接收其他页面传过来的内容
console.log(res)
})
</script>
</body>
</html>
(4)通过 nginx 反向代理
// proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
(5)通过 node 中间件代理
(6)通过 window.name + iframe,只适用于 iframe
(7)通过 location.hash + iframe,只适用于 iframe
(8)通过 document.domain + iframe
此方式只能用于二级域名,协议、端口相同的情况下,比如 a.baidu.com 和 b.baidu.com,只需要给需要通信的页面加上 document.domain ='baidu.com'
父页面 01.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>我是01.html</h2>
<button onclick="handleMessage()">获取内容</button>
<iframe
src="http://localhost:5000/02.html"
style="width: 300px; height: 300px"
></iframe>
<script>
document.domain = 'baidu.com'
function handleMessage() {
const iframe = document.getElementsByTagName('iframe')[0]
iframe.contentWindow.a
}
</script>
</body>
</html>
子页面02
<script>
document.domain = 'baidu.com'
var a = 3
</script>