前端跨域 問題及多種解決方案,你get到了嗎
原理: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");
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
<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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
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");
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
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屬性
- 1
- 2
- 3
- 4
- 5
- 6
<!-- 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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
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('後臺數據');
})
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
傳送數據
<script>
let socket = new WebSocket('ws://localhost:3333');
socket.onopen = function () {
socket.send('前端數據');
}
socket.onmessage = ({ data }) => {
console.log(data);//後臺數據
}
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27