跨域請求
全稱:非同源策略請求(同源即同協(xié)議、同域名和同端口),而ajax專門用于處理同源策略請求,因此對于非同源請求,特別是在前后端分離的項(xiàng)目當(dāng)中,我們就會面臨這類問題。
這里要聲明一下:跨域是瀏覽器行為,而不是服務(wù)器行為(當(dāng)前臺向服務(wù)器發(fā)起請求時(shí),后臺并沒有阻止請求操作,但后臺返回?cái)?shù)據(jù)時(shí),瀏覽器基于安全考慮,若非同源請求,將可能禁止對請求數(shù)據(jù)的處理)
常用跨域解決方案
基于Jsonp
我們可以發(fā)現(xiàn)在html當(dāng)中含有src
屬性的標(biāo)簽,如<srcipt>
/<img>
/<link>
/<iframe>
標(biāo)簽,在請求資源時(shí)都不存在跨域請求的限制。而jsonp則是基于<script>
標(biāo)簽去請求資源,服務(wù)器則在返回?cái)?shù)據(jù)資源時(shí)將其包在一個(gè)本地可調(diào)用的全局函數(shù)里返回,然后本地則調(diào)用這個(gè)函數(shù)
原理詳解
首先因由于同源策略,我們無法通過ajax請求來獲取不符合條件的資源,但是假如我們用<script>
標(biāo)簽請求的js文件中有這樣的語句:
test({"x": 1})
而我們前臺的請求如下:
<script src="http://xxx.xxx.com/xxx.js"></script>
那么獲取到請求以后就會執(zhí)行test
這個(gè)函數(shù),并將一個(gè)對象作為參數(shù)傳入,此時(shí)如果我們的前臺本身有test
這個(gè)函數(shù),那么就可以成功執(zhí)行這段代碼。換個(gè)說法,如果現(xiàn)在我們通過訪問一個(gè)后臺接口獲取到跟前面一樣的字符串(之前是通過訪問一個(gè)遠(yuǎn)程js資源得到的),那么因?yàn)槎际怯?code><script>標(biāo)簽獲取的,獲取的結(jié)果也一樣,只是請求的url稍微有些改變,可以發(fā)現(xiàn)結(jié)果都是是一樣的:正常執(zhí)行test
這個(gè)函數(shù),例如下面這段代碼:
<script src="http://xxx.xxx.com/api"></script>
<script>
function test(data) {
console.log(data);
}
</script>
那么控制臺就會成功輸出data的內(nèi)容。這就是jsonp的原理,可以看出和ajax請求的本質(zhì)是完全不一樣的,ajax是基于XmlHttpRequest,而jsonp則是基于標(biāo)簽動(dòng)態(tài)請求,可以說是一種偽請求
缺點(diǎn):由于是基于標(biāo)簽的src屬性請求的,只能使用get請求、并且安全性不好
基于CORS跨域資源共享配置
CORS是十分常用的一種跨域解決方案,其只需要在服務(wù)端配置對應(yīng)的響應(yīng)頭屬性即可,而無需前端做任何操作,最關(guān)鍵的就是在返回頭里配置Access-Control-Allow-Origin
屬性,舉例(這里基于flask進(jìn)行示例):
from flask import Flask, make_response
import json
app = Flask(__name__)
@app.route('/test', methods=['GET'])
def user_info():
response = make_response(json.dumps({'data': 'content'}))
response.headers['Access-Control-Allow-Origin'] = '*'
return response
if __name__ == '__main__':
app.run(debug=True, port=5000)
還有一些其他訪問控制的配置如下:
Access-Control-Allow-Methods: POST, GET, PUT, DELETE, HEAD, OPTIONS
# 允許的請求方式
#(注意只有POST, GET以及HEAD是默認(rèn)允許的請求,其他的請求都必須先發(fā)送一個(gè)OPTIONS的預(yù)請求
# 當(dāng)預(yù)請求得到認(rèn)可后才可以進(jìn)行請求,而只有在該配置當(dāng)中配置的方法才會得到認(rèn)可)
Access-Control-Allow-Headers: 'Content-Type, ...'
# 允許請求的頭部信息,沒有設(shè)置在里面的header都是不允許的
Access-Control-Max-Age: '1000'
# 設(shè)置允許跨域的時(shí)間,此時(shí)在有效期內(nèi)無需再發(fā)送預(yù)請求進(jìn)行驗(yàn)證,直接發(fā)請求就可以了,單位是s
Access-Control-Allow-Credentials: true
# 是否允許發(fā)送cookie
詳細(xì)參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
缺點(diǎn):cors配置源地址只能配置*
(多源)或者一個(gè)地址,并且如果配置的是*
,那么基于安全性問題,請求時(shí)將無法攜帶cookie
注:
如果不想配置成*
,又希望多個(gè)地址可以,那么可以在程序當(dāng)中判斷訪問的地址是否是允許的,如果是,則加入到cors配置當(dāng)中
基于Http Proxy
由于cors的弊端,又出了Http Proxy的方案,需要在webpack中配置,常在基于vue、react的前后端分離項(xiàng)目中使用,首先要安裝對應(yīng)模塊包:
npm install webpack webpack-dev-server
然后在webpack的配置文件webpack.config.js
中配置:
devServer: {
port: 8080,
proxy: {
'/': {
target: 'http://127.0.0.1:6666',
// 代理的地址
// secure: false,
// 如果是https接口,需要配置這個(gè)參數(shù)
changeOrigin: true
// 是否跨域
}
}
}
vue-cli中配置
如果是基于vue-cli的項(xiàng)目工程,那么這里介紹兩種方式實(shí)現(xiàn)proxy
代理的配置:
- 第一種:配置
devServer
,在build/webpack.base.conf.js
中的module.exports
進(jìn)行和上面相同的配置 - 第二種:配置
proxyTable
,直接在config/index.js
中的module.exports.dev
配置如下:
module.exports = {
dev: {
...
proxyTable: {
// 代理配置
'/': {
target: 'http://127.0.0.1:6666',
changeOrigin: true,
// pathRewrite: {
// '^/apis': '/api' //重寫的路徑
// }
}
},
},
...
}
注:
配置proxy之后,實(shí)際上前端請求還是發(fā)給本身的node服務(wù)器(查看網(wǎng)絡(luò)請求就可以發(fā)現(xiàn)),例如前端是8080
,配置了6666
的代理,那么前端會先請求本身,即8080
,然后8080
再通過node去訪問6666
,由于服務(wù)器之間的請求不存在跨域問題,所以6666
返回?cái)?shù)據(jù)給node,node再返回給前端,由于同源,所以此時(shí)數(shù)據(jù)也就沒有跨域問題了
注:
在vue-cli3.0+以后,則直接在項(xiàng)目根目錄下創(chuàng)建文件vue.config.js
,并添加如下內(nèi)容即可:
module.exports = {
devServer: {
proxy: {
"/": {
target: "http://127.0.0.1:6666/",
changeOrigin: true
}
}
}
};
基于nginx配置CORS
在nginx中也可以配置cors
,舉例:
server {
listen 80;
server_name localhost;
...
location / {
root html;
index index.html index.htm;
proxy_pass http://xxx;
add_header Access-Control-Allow-Origin *; # 配置cors
...
}
基于nginx配置反向代理
假如前臺服務(wù)端口為http://127.0.0.1:3000
,后臺服務(wù)端接口為http://127.0.0.1:8000/api
,那么因?yàn)椴煌矗厝淮嬖诳缬騿栴},此時(shí)可以在nginx中進(jìn)行配置如下:監(jiān)聽3000
端口,并配置/api
的反向代理地址為:http://127.0.0.1:8000/api
,配置文件示例如下:
server {
listen 3000;
server_name localhost;
...
location /api/{
...
proxy_pass http://127.0.0.1:8000/api/; # 配置反向代理
}
}
再將前臺請求的接口改為:http://127.0.0.1:3000/api
,即可解決跨域問題。
(原理:經(jīng)過nginx的反向代理配置,現(xiàn)在訪問http://127.0.0.1:3000/api
就相當(dāng)于訪問http://127.0.0.1:8000/api
,而前臺請求的url因?yàn)楦某闪?code>http://127.0.0.1:3000/api,在瀏覽器看來前臺和請求接口同源,也就不存在跨域問題了)
其他跨域解決方案
基于修改本地host文件(不推薦)
例如前臺地址:127.0.0.1:6666
,而后臺接口地址:http://api.xxx.com
,此時(shí)如果想要訪問后臺接口,可以在本地host文件中加一行:
127.0.0.1:6666 http://api.xxx.com
但這種方式實(shí)際上只是在模仿同源請求,并沒有實(shí)質(zhì)地解決跨域問題
基于Iframe的postMessage
postMessage方法允許頁面間基于Iframe進(jìn)行消息傳遞,例如A和B頁面進(jìn)行消息傳遞:
- a.html
<html>
<body>
<iframe
id="iframe"
src="http://127.0.0.1:5500/b.html"
style="display: none;"
></iframe>
</body>
<script>
iframe.onload = function() {
iframe.contentWindow.postMessage("來自A的信息...", "http://127.0.0.1:5500/b.html");
// 發(fā)送消息給頁面B
};
// 監(jiān)聽頁面B發(fā)來的消息
window.onmessage = function(ev) {
console.log("A收到B的信息:", ev.data);
};
</script>
</html>
- b.html
<html>
<body></body>
<script>
// 監(jiān)聽頁面A發(fā)來的消息
window.onmessage = function(ev) {
console.log("B收到A的信息:", ev.data);
ev.source.postMessage("回信...", "*");
};
</script>
</html>
基于H5的web socket
由于原生web socket
不太好使用,這里使用socket.io
(對web socket
進(jìn)行了封裝的框架)進(jìn)行示例:
- 服務(wù)端(基于node):
const server = require("http").createServer();
const io = require("socket.io")(server);
io.on("connection", client => {
client.on("event", data => {
console.log(data);
});
client.on("message", msg => {
console.log(msg);
});
client.on("disconnect", () => {
console.log("server has closed!");
});
});
server.listen(3000);
- 客戶端:
<html>
<body></body>
<script src="https://cdn.bootcss.com/socket.io/2.3.0/socket.io.js"></script>
<script>
let socket = io('http://127.0.0.1:3000/');
socket.on('connect', () => {
socket.on('message', msg => {
console.log(msg);
})
socket.on('disconnect', () => {
console.log('server has closed!');
})
})
socket.send('test');
</script>
</html>
web socket使用參考:https://zhuanlan.zhihu.com/p/74326818
使用WebSocket進(jìn)行跨域數(shù)據(jù)請求參考:https://blog.csdn.net/itkingone/article/details/83818278