前端跨域問題各種解決方式及原理
跨域的各種解決方式及原理
因為瀏覽器有某些安全級別的限制,例如,同源策略,所以在進行瀏覽器端的web應用開發的時候,經常會遇到跨域問題。
同源策略:只有在同源的情況下(同域名,同協議,同端口)才能進行數據交互
跨域問題報錯:XMLHttpRequest cannot load https://api.douban.com/v2/movie/in_theaters. No ‘Access-Control-Allow-Origin‘ header is present on the requested resource. Origin ‘http://127.0.0.1:5500‘ is therefore not allowed access.
當我們在公司開發的時候,很可能因為後端的服務器和我們的本地服務器不同源所以產生跨域問題,還有當我們公司的服務器有很多,其中很可能會有單獨的數據服務器,開發應用的時候必然會有跨域,所以解決跨域問題是前端工程師必要的技能。
常用的跨域方式:jsonp,cors,服務端代理
jsonp(JSON with Padding)
jsonp是一種前後端結合的跨域問題解決方式
原理:動態的創建script標簽,將script標簽的src屬性設置成請求的目標地址,和後端商議之後,設置callback回調函數,利用回調函數來接收數據並進行使用
為什麽使用script:
依靠html中標簽的src屬性不受同源策略的影響來實現的,而script標簽接收到數據之後可以進行數據的處理,所以一般選用script標簽,
為什麽要動態的創建script標簽:
script只能執行一次或者說只能請求一次,所以說當我們要不斷的進行jsonp請求的時候,每一次的請求都需要一個script標簽,所以需要動態去創建script標簽,script標簽能將請求到的字符串數據當成js代碼去運行,所以我們可以依靠後端返回一段(執行某個函數,且給此函數傳入數據)的這樣一段字符串來實現,這樣的話,script請求到該字符串之後,就會執行該函數,且該函數能接收到數據。
為什麽需要和後端商議之後才能設置callback函數:
因為後端開發者並不知道前端準備來接收數據的函數是哪個函數,所以需要讓前端通知後端接收數據的函數名,前端需要將函數名傳遞給後端,但是傳遞的時候是以鍵值對的方式傳遞過去的,所以需要前端將鍵值對的鍵名事先告知後端
註意:獲取數據且操作完成後,一定要將創建的script標簽去掉,將隨機函數的內存給釋放
缺點: 只能做get請求
CORS
這是一種純後端的跨域方式
因為每次請求的時候請求頭信息都會被攜帶到目標服務器,目標服務器經過判斷選擇是否運行訪問,不允許的時候就出現了跨域問題,所以我們可以可以讓後端開發人員對後端目標服務器進行設置,使其能識別我們的服務器的請求頭並允許訪問,這就是cors,cors主要是靠後端設置:Access-Control-Allow-Origin
node:
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
res.writeHead(200,{‘Content-Type‘:‘application/json; charset=utf-8‘})
let data = {status:1,data:‘hello world‘}
res.end(JSON.stringify(data))
proxy
這也屬於服務器代理的跨域處理方式
因為服務端之間的數據請求沒有跨域限制,所以我們借助一個中間的代理服務器去像目標服務器發送請求:
前端代碼:
let target = ‘https://api.douban.com/v2/book/search‘
let proxy = ‘http://localhost:3000‘
$.ajax({
url:proxy+‘/v2/book/search‘,//向proxy服務器發送請求,後面的path之類的其實是target域的path
data:{
target:‘https://api.douban.com‘,//告訴proxy服務器,我們真正像請求的目標服務器是誰
tag:‘勵誌‘
},
success(result){
console.log(result)
}
})
代理服務器:
const http = require(‘http‘)
const url_util = require(‘url‘)
const path = require(‘path‘)
const cross = require(‘./module/cross‘)
http.createServer((req,res)=>{
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
// http://localhost:3000/v2/book/search?target=..params=...
let url_info = url_util.parse(req.url,true)
let {target} = url_info.query
delete url_info.query.target
let params = url_info.query
//向真正的目標服務器發送請求
let real_url = target+url_info.pathname
let method = req.method
cross(real_url,method,params,(result)=>{
res.writeHead(200,{‘Content-Type‘:"application/json;charset=utf8"})
res.end(JSON.stringify(result))
})
}).listen(3000)
cross跨域模塊工具:
const url_util = require(‘url‘)
const qs = require(‘querystring‘)
const cross = (url,method,params,cb)=>{
//向url發送請求
//目標源的協議
let url_info = url_util.parse(url)
let protocol = url_info.protocol.replace(‘:‘,‘‘)
//引入到對應的模塊
let http = require(protocol)
let data = qs.stringify(params)
let options = {
hostname: url_info.hostname,
port: url_info.port||((protocol)=>{return protocol==‘http‘?80:443})(protocol),
path: url_info.path,
method: method
}
if(method==‘post‘){
options.headers = {
‘Content-Type‘: ‘application/x-www-form-urlencoded‘,
‘Content-Length‘: data.length
}
}else{
let symbol = url_info.path.indexOf(‘?‘)>=0?‘&‘:‘?‘
options.path = url_info.path+symbol+data
console.log(options.path)
}
let req = http.request(options,(res)=>{
let result = ‘‘
res.on(‘data‘,(chunk)=>{result+=chunk})
res.on(‘end‘,(chunk)=>{cb(result)})
})
if(method==‘post‘){
req.write(data)
}
req.end()
}
module.exports = cross
前端跨域問題各種解決方式及原理