cross-origin
Why
為什么會(huì)存在跨域問(wèn)題
- 同源策略
由于出于安全考慮愚铡,瀏覽器規(guī)定JavaScript不能操作其他域下的頁(yè)面DOM伶授,不能接受其他域下的xhr請(qǐng)求(不只是js,引用非同域下的字體文件,還有canvas引用非同域下的圖片峻呕,也被同源策略所約束)
只要協(xié)議、域名趣效、端口有一者不同瘦癌,就被視為非同域。
How
如何解決
要解決跨域問(wèn)題英支,就要繞過(guò)瀏覽器對(duì)js的限制佩憾,另辟蹊徑
- JSONP
這是最簡(jiǎn)單,也是最流行的跨域解決方案干花,它利用script標(biāo)簽不受同源策略的影響妄帘,解決跨域,需要后臺(tái)配合池凄,返回特殊格式的數(shù)據(jù)
前端
<script>
function JSONP(link) {
let script=document.createElement("script");
script.src=link;
document.body.appendChild(script);
}
function getUser(data) {
console.log(data);// todo
}
const API_URL_USER='http://cache.video.iqiyi.com/jp/avlist/202861101/1/?callback=getUser'; // 這里以愛奇藝的接口為例(來(lái)源網(wǎng)絡(luò)抡驼,侵刪)
JSONP(API_URL_USER);
</script>
后端
// Express(Nodejs)
// mock data
const USERS=[
{name:"Tom",age:23},
{name:"Jack",age:23}
];
app.get("/user",function (req,res) {
let cbName=req.query["callback"];
// 這里做一個(gè)容錯(cuò)處理
res.send(`
try{
${cbName}(${JSON.stringify(USRES)});
}catch(ex) {
console.error("The data is invalid");
}
`);
});
- CORS (cross-origin resource sharing)
跨域資源共享,是W3C的一個(gè)標(biāo)準(zhǔn)肿仑,它允許瀏覽器發(fā)送跨域服務(wù)器的請(qǐng)求致盟,CORS需要瀏覽器和服務(wù)器同時(shí)支持
后端
簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求詳情,請(qǐng)閱讀阮一峰老師的博文尤慰,這里不再敖述
app.use(function (req,res,next){
res.header('Access-Control-Allow-Origin', 'http://localhost:6666'); // 允許跨域的白名單馏锡,一般不建議使用 * 號(hào)
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); // 允許請(qǐng)求的方法,非簡(jiǎn)單請(qǐng)求伟端,會(huì)進(jìn)行預(yù)檢
res.header('Access-Control-Allow-Headers', 'Content-Type'); // 允許請(qǐng)求攜帶的頭信息杯道,非簡(jiǎn)單請(qǐng)求,會(huì)進(jìn)行預(yù)檢
res.header('Access-Control-Allow-Credentials','true'); // 允許發(fā)送cookie责蝠,這里前端xhr也需要一起配置 `xhr.withCredentials=true`
next();
});
- 代理
只要是在與你同域下的服務(wù)器党巾,新建一個(gè)代理(服務(wù)端不存在同源策略),將你的跨域請(qǐng)求全部代理轉(zhuǎn)發(fā)
后端
const proxy=require("http-proxy-middleware"); // 這里使用這個(gè)中間件完成代理
app.use('/api', proxy("http://b.com")); // http://a.com/api -> http://b.com/api
- window.name+iframe
MDN里解釋道它是獲取/設(shè)置窗口的名稱
霜医,因?yàn)榈乃诓煌?yè)面甚至域名加載后值都不會(huì)改變齿拂,該屬性也被用于作為 JSONP 的一個(gè)更安全的備選來(lái)提供跨域通信(cross-domain messaging)
前端
<!--http://a.com/page1.html-->
<script>
function request(url,callback) {
let iframe=document.createElement("iframe");
let isFirst=true;
iframe.style.display="none";
iframe.addEventListener("load",function () {
if (isFirst) {
isFirst=false; // 防止iframe循環(huán)加載
iframe.src="http://a.com/page2.html";
callback && callback(iframe.contentWindow.name);
iframe.remove();
}
});
iframe.src=url;
}
requeset("http://b.com/user",function (data) {
console.log(data); // todo
});
</script>
后端
// Express(Nodejs)
// mock data
const USERS=[
{name:"Tom",age:23},
{name:"Jack",age:23}
];
app.get("/user",function (req,res) {
res.send(`
<script>
;window.name=${JSON.stringify(USERS)};
</script>
`);
});
document.domian
這個(gè)使用情況有限,例如
http://a.c.com
http://b.c.com
主域相同時(shí)肴敛,分別設(shè)置他們頁(yè)面的document.domain="c.com";
locaction.hash+iframe
嵌套兩層iframe署海,達(dá)到第一層與第三層同域,就可以互相通信了
<!--http://a.com/page1.html-->
<script>
let iframe=document.createElement("iframe");
iframe.style.display="none";
iframe.src="http://b.com/user.html";
window.addEventListener("hashchange",function () {
console.log(location.hash.slice(1)); // todo
});
</script>
<!--http://b.com/user.html-->
<script>
let iframe=document.createElement("iframe");
iframe.style.display="none";
function getUserData() {
fetch("http://b.com/user")
.then(res=>{
let data=res.json();
iframe.src=`http://a.com/page2.html#${data}`;
});
}
getUserData();
window.addEventListener("hashchange",function () {
getUserData();
});
</script>
<script>
top.location.hash=window.location.hash;
</script>
- 圖片ping
這個(gè)只能發(fā)出去請(qǐng)求,無(wú)法獲取到服務(wù)器的響應(yīng)叹侄,常常用于網(wǎng)站流量統(tǒng)計(jì)
let img=new Image();
img.addEventListener("load",function () {
console.log("Send success"); // todo
});
img.src="http://site.c.com/a.gif?count=666";
- postMessage+iframe
<!-- http://a.com -->
<button id="sendBtn">從B接口獲取用戶數(shù)據(jù)</button>
<iframe src="http://b.com" id="ifr"></iframe>
<script>
window.addEventListener("message",function({detail,origin}){
if (origin==="http://b.com") { // 最好判斷下消息來(lái)源
if (detail.type==="set-user") {
console.log(detail.data); // todo
}
}
});
sendBtn.addEventListener("click",function () {
ifr.contentWindow.postMessage({
type:"get-user",
},"http://b.com");
});
</script>
<!-- http://b.com -->
<script>
window.addEventListener("messagae",function({detail,origin}){
if (origin==="http://a.com") { // 最好判斷下消息來(lái)源
if (detail.type==="get-user") {
fetch("http://b.com/user")
.then(res=>{
top.contentWindow.postMessage({
type:"set-user",
data:res.json(), // 假設(shè)接口返回的是json格式的數(shù)據(jù)
},"http://a.com");
})
}
}
});
</script>
- postMessage+form+iframe
這個(gè)需要后臺(tái)配合返回特殊格式的數(shù)據(jù)巩搏,TL,DR 可以看這個(gè)demo
- WebSocket
WebSocket是一種通信協(xié)議,該協(xié)議不實(shí)行同源政策趾代,
注意需要瀏覽器和服務(wù)器都支持的情況下
<script src="http://cdn.bootcss.com/socket.io/1.7.2/socket.io.min.js"></script>
<script>
var io = io.connect('http://b.com');
io.on('data', function (data) {
console.log(data); // 接受來(lái)自服務(wù)器的消息
});
</script>
后端
// Nodejs
const server = require('http').createServer();
const io = require('socket.io')(server);
io.on('connection', function (client) {
client.emit('data', 'This message from "http://b.com"');
});
Summary
- 目前個(gè)人在工作中遇到的解決方法就是這些贯底,當(dāng)然還有許多其他的方法,你看撒强,其實(shí)跨域并不難吧 _
- js通過(guò)xhr發(fā)的跨域請(qǐng)求禽捆,雖然得不到響應(yīng),但是可以發(fā)送出去飘哨,其實(shí)如果是單向通信的話胚想,也可以,比如文章閱讀統(tǒng)計(jì)芽隆,網(wǎng)站流量統(tǒng)計(jì)
Reference
跨域資源共享 CORS 詳解---阮一峰
不要再問(wèn)我跨域的問(wèn)題了
FatDong1/cross-domain