在我們日常開發中,經常碰到一些跨域的問題。
例如www.a.com
想要獲取到www.b.com
的接口或者數據。就會因爲同源策略的原因而無法獲取到。
什麼是同源策略
同源策略 same-orgin policy
不同域的客戶端腳本在沒有明確授權的情況下,不能讀寫對方的資源。
舉例
地址 | 是否可以請求 |
---|---|
https://a.taobao.com | 不可以-協議不同 |
http://www.taobao.com | 不可以-子域不同 |
http://taobao.com | 不可以-子域不同 |
http://a.taobao.com:8080 | 不可以-端口不同 |
http://a.taobao.com/music/ | 可以-同域 |
CORS
CORS是一種跨域的解決方案,其原理就是前臺發送一個請求,而服務器就返回一個請求頭授權訪問。
- 客戶端請求一個跨域的接口(默認帶有Origin請求頭)
- 服務端收到後設置一個響應頭並返回(Access-Control-Allow-Origin)授權
// 後臺koa腳本
const koa = require('koa');
const bodyParser = require('koa-bodyparser');
const app = new koa();
//使用bodyParser
app.use(bodyParser());
app.use(async ctx =>{
const url = ctx.url;
if(ctx.headers.origin && ctx.query.cors){
//設置請求頭
ctx.set('Access-Control-Allow-origin',ctx.headers.origin)
}
let res = {
code:0,
data:'success'
}
ctx.body = JSON.stringify(res);
})
app.listen(3000,()=>{
console.log('服務器衝起來了');
})
後臺在
node
3000的端口運行,前臺在http-server
3001 端口運行
//前臺請求
var $ = (id) => document.getElementById(id);
var btn = $('button1');
var btn2 = $('button2');
function getData(callback, cors) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.responseText) {
callback(JSON.parse(xhr.responseText), xhr);
}
}
xhr.open('get', `http://127.0.0.1:3000/${cors}`);
xhr.send(null);
}
btn.addEventListener('click', () => {
getData((response) => {
console.log(`${response.data}`)
}, '');
});
btn2.addEventListener('click', () => {
getData((response) => {
console.log(`${response.data}`)
}, '?cors=1');
});
- CORS 優點在於方便簡潔,只需要服務端設置響應頭就好了。
- 缺點在與一些IE不兼容 具體可以看 👉 CORS兼容性
JSONP
原理
- 通過
script
進行請求通過src請求 - 創建一個回調函數,然後在遠程服務上調用這個函數並且將JSON 數據形式作爲參數傳遞
- 將JSON數據填充進回調函數
//koa
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
// 使用bodyParser
app.use(bodyParser())
app.use(async ctx => {
const url = ctx.url
if (url.indexOf('/getData') === 0) { // 接口名稱
ctx.set('Content-Type', 'application/x-javascript')
let res = {
code:0,
data:"我是一個jsonP跨域的數據!"
}
ctx.body = `${ctx.query.callback || 'jsonp'}(${JSON.stringify(res)})`
} else {
ctx.status = 404
ctx.body = '404'
}
})
app.listen(3000, () => {
console.log('服務啓動,打開 http://127.0.0.1:3000/')
})
後臺在
node
3000的端口運行,前臺在http-server
8081 端口運行
/**
* 自動發送 jsonp
* @param {String} url
* @param {Obj} data
* @param {Function} callback
*/
function jsonp (url, data, callback) {
var funcName = getFunctionName()
data = data || {}
data.callback = funcName
url = parseUrl(url, serialize(data))
window[funcName] = function (response) {
// 這裏可以看情況處理,比如如果是 jsonp 我們可以 parse 一下
// data = JSON.parse(response)
callback(response)
}
createScript(url)
}
/**
* 序列化參數
* jsonp 中參數只能是 GET 方式傳遞
* @param {Obj} data
*/
function serialize (data) {
var ret = []
Object.keys(data).forEach(item => {
ret.push(encodeURIComponent(item) + '=' + encodeURIComponent(data[item]))
})
return ret.join('&')
}
/**
* 處理 URL ,把參數拼上來
* @param {String} url
* @param {String} param
*/
function parseUrl (url, param) {
return url + (url.indexOf('?') === -1 ? '?' : '&') + param
}
/**
* 必須要有一個全局函數,而且不能重名
*/
function getFunctionName () {
return ('jsonp_' + Math.random()).replace('.', '')
}
/**
* 創建 script 標籤並插到 body 中
* @param {String} url
*/
function createScript (url) {
var doc = document
var script = doc.createElement('script')
script.src = url
doc.body.appendChild(script)
}
//調用
jsonp('http://127.0.0.1:3000/getData', {id:1}, (data) => {
console.log(data)
})
這是一個簡單版的
jsonP
的實現,面試中也會常常被問到,並且手擼一個jsonP
具體的一個方法實現推薦一個 github 上面的一個庫 👉 實現jsonP
iframe
原理
- 藉助iframe標籤進行跨域
- 將獲取到的數據掛載到window.name
我們以三個頁面爲例和一個json爲例
- a.html -> 在端口 3001 運行
- b.html -> 在端口 3002 運行
- c.html
- data.json
- c去去掉用父級的方法,並傳遞獲取到的數據。
大概的交互圖爲這樣 👇
a爲請求數據
<!--a.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>我是A頁面</title>
</head>
<body>
<div></div>
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(xhr.responseText);
} else {
console.log(xhr.status, xhr.statusText);
}
}
xhr.open('get', 'http://127.0.0.1:3002/data.json');
xhr.send(null);
</script>
</body>
</html>
b爲處理數據
<!--b.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>處理數據</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
window.name = xhr.responseText;
location.href = 'http://127.0.0.1:3001/c.html';
} else {
console.log(xhr.status, xhr.statusText);
}
}
xhr.open('get', 'data.json');
xhr.send(null);
</script>
</body>
</html>
c爲更新數據
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>傳遞數據</title>
</head>
<body>
<script>
parent.update(window.name);
</script>
</body>
</html>
data數據
[
{
"data": "我是一條跨域數據"
}
]
當我們調用a.html第一個AJAX方法的時候,毫無疑問肯定是跨域的,因爲端口不一樣
Access to XMLHttpRequest at ‘http://127.0.0.1:3002/data.json’ from origin ‘http://127.0.0.1:3001’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
接下來我們修改一下A 的代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>我是A頁面</title>
</head>
<body>
<div></div>
<iframe src="http://127.0.0.1:3002/b.html" frameborder="0"></iframe>
<script>
function update(data) {
data = JSON.parse(data);
var html = ['<ul>'];
html.push(`<li>${data[0].data}</li>`);
html.push('</ul>');
document.querySelector('div').innerHTML = html.join('');
}
</script>
</body>
</html>
在改變了邏輯時
a.html
嵌套了一個與接口一樣端口的網頁b.html
通過同端口的方式請求到了想用端口的數據,並且掛載到了全局的window.name
上,並且跳轉到c.html
c.html
通過parent
調用a.html
的update
的方法並把window.name
值一同傳遞
此時我們就大功告成啦,頁面顯示出data.json
的數據
以上就是我分享的前端跨域請求方式!
寫的不好,僅供參考!