日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

跨域,對(duì)于正在學(xué)習(xí)或者已經(jīng)就業(yè)的前端同學(xué)而言,就是老朋友。只要涉及“請(qǐng)求”“前后端交互”“開(kāi)發(fā)階段”等關(guān)鍵字,都避不開(kāi)跨域。同時(shí)它也是面試中最常出現(xiàn)的考點(diǎn)之一,面試官可以通過(guò)跨域,了解應(yīng)聘者對(duì)網(wǎng)絡(luò)協(xié)議、網(wǎng)絡(luò)安全等概念的理解。

跨域并不是阻礙前后端交互的障礙,什么是跨域,怎么避開(kāi)跨域帶來(lái)的不便,本文主要細(xì)解三種主流的解決方案:JSONP,CORS,代理服務(wù)器,細(xì)致地解開(kāi)跨域相關(guān)的迷惑。

一、同源策略

同源策略是一個(gè)重要的安全策略,它用于限制一個(gè)Origin的文檔或者它加載的腳本如何能與另一個(gè)源的資源進(jìn)行交互。它能幫助阻隔惡意文檔,減少可能被攻擊的媒介。

Origin:指web文檔的來(lái)源,Web 內(nèi)容的來(lái)源取決于訪問(wèn)的URL的方案 (協(xié)議),主機(jī) (域名) 和端口定義。只有當(dāng)方案,主機(jī)和端口都匹配時(shí),兩個(gè)對(duì)象具有相同的起源。

二、跨域

關(guān)于URL是否同源,根據(jù)上圖中的①②③進(jìn)行判斷即可,只要有一點(diǎn)不同,就達(dá)到跨域的條件。順帶一提,即便是向域名對(duì)應(yīng)的ip進(jìn)行資源請(qǐng)求,仍然會(huì)跨域。

IE的特殊性:Inte.NET Explorer 的同源策略有兩點(diǎn)差異,一是IE未將端口號(hào)納入同源策略的檢查,其次是兩個(gè)高度互信的域名也不受同源策略的檢查。

常見(jiàn)的跨域情景:

瀏覽器內(nèi)常見(jiàn)的跨域報(bào)錯(cuò):

跨域出現(xiàn)的場(chǎng)景:

一般常見(jiàn)于開(kāi)發(fā)階段,本地啟動(dòng)項(xiàng)目后,當(dāng)前頁(yè)面域名和后臺(tái)服務(wù)器域名不相同,導(dǎo)致跨域。在項(xiàng)目上線后,會(huì)通過(guò)統(tǒng)一域名、后端配置域名白名單等方式避免跨域。

下方的解決方案中,我們通過(guò)koa2框架搭建服務(wù)器,實(shí)現(xiàn)一系列的情景模擬。

三、跨域的解決方案

1.JSONP

原理:通過(guò)script標(biāo)簽沒(méi)有跨域限制的特性,進(jìn)行資源的請(qǐng)求和獲取。

限制:需要目標(biāo)服務(wù)器進(jìn)行配合,且僅支持get請(qǐng)求

我們直接通過(guò)代碼和注釋?zhuān)斫鈐sonp的使用前端代碼如下:

<script>

window.jsonp = function(res){

console.log(res);

}

</script>

<script src="http://localhost:9527/jsonp?val=123&cb=jsonp"></script>

后端代碼如下:

// 定義jsonp接口

router.get('/jsonp', async (ctx, next) => {

/*

1.后端通過(guò)query獲取前端傳來(lái)的請(qǐng)求參數(shù)

其中包括:

· 交予后端進(jìn)行功能邏輯操作的數(shù)據(jù),如val

· 交予后端進(jìn)行jsonp操作的函數(shù)名,如cb

*/

const {cb, val} = ctx.query

// 2.調(diào)用回調(diào)函數(shù),進(jìn)行傳參,將處理好的數(shù)據(jù)返回給前端

if(val === '123'){

const requestData = {

code: 10001,

data: '登陸成功'

}

//在響應(yīng)體中觸發(fā)目標(biāo)函數(shù),并將處理好的數(shù)據(jù)requestData作為實(shí)參傳入

ctx.body = `${cb}(${JSON.stringify(requestData)})`;

}

})

前端通過(guò)window對(duì)象,在全局掛載了一個(gè)待觸發(fā)的函數(shù)。

后端通過(guò)響應(yīng)體觸發(fā)這個(gè)函數(shù),并將數(shù)據(jù)作為入?yún)ⅲ瑐鹘o前端。

了解簡(jiǎn)單的實(shí)現(xiàn)后,前端可以對(duì)jsonp的功能再進(jìn)行一層封裝:

/*

1. 生成script標(biāo)簽,我們需要script標(biāo)簽進(jìn)行接口的調(diào)用

2. 處理參數(shù)數(shù)據(jù),分別整理好接口,接口參數(shù),函數(shù)名等數(shù)據(jù),并進(jìn)行填充

3. 寫(xiě)入生成好的script標(biāo)簽,實(shí)現(xiàn)接口的調(diào)用(返回promise對(duì)象,便于鏈?zhǔn)秸{(diào)用)

4. 清除script標(biāo)簽

*/

function jsonp(requestData) {

// 對(duì)傳入?yún)?shù)進(jìn)行處理

const { url, data, jsonp } = requestData;

let query = '';

for (let key in data) {

query += `${key}=${data[key]}&`;

}

const src = `${url}?${query}jsonp=${jsonp}`;

// 生成、填充script標(biāo)簽,在頁(yè)面中掛載調(diào)用接口

let scriptTag = document.('script');

scriptTag.src = src;

document.body.(scriptTag);

return new Promise((resolve, reject) => {

window[jsonp] = function(rest){

resolve(rest)

document.body.removeChild(scriptTag)

}

})

}

// 整理數(shù)據(jù)

const requestData = {

url: 'http://localhost:9527/jsonp',

data: {

val: 123,

},

jsonp: 'getMessage'

}

// 接口調(diào)用

btn.onclick = function () {

jsonp(requestData).then(function (response) {

console.log(response);

})

}

2.CORS

Cross-Origin Resource sharing(跨域資源共享),是一種基于HTTP頭的機(jī)制,該機(jī)制允許服務(wù)器標(biāo)示除了它自己以外其他origin(域名,協(xié)議和端口),既瀏覽器在跨域的情景下仍然能從目標(biāo)服務(wù)器請(qǐng)求并獲取資源。

而對(duì)服務(wù)器數(shù)據(jù)可能產(chǎn)生副作用的HTTP請(qǐng)求方法,都會(huì)觸發(fā)CORS中的預(yù)檢機(jī)制。

CORS中通過(guò)預(yù)檢機(jī)制(preflight request)檢查服務(wù)器是否允許瀏覽器發(fā)送真實(shí)請(qǐng)求,瀏覽器會(huì)先發(fā)送一個(gè)預(yù)檢請(qǐng)求(option請(qǐng)求),請(qǐng)求中會(huì)攜帶真實(shí)請(qǐng)求的請(qǐng)求信息:

origin:請(qǐng)求的來(lái)源

Access-Control-Request-Method:

通知服務(wù)器在真正的請(qǐng)求中會(huì)采用哪種HTTP方法(GET,POST,DELETE...)

Access-Control-Request-Headers:通知服務(wù)器在真正的請(qǐng)求中會(huì)采用哪些請(qǐng)求頭

服務(wù)器可以在預(yù)檢請(qǐng)求中,可以根據(jù)以上三條信息,確定預(yù)檢請(qǐng)求是否通過(guò):

//server.js

App.use(async (ctx, next) => {

// 允許跨域資源共享的白名單

const whiteList = ['http://127.0.0.1:5500']

// 判斷目標(biāo)源是否通行

const pass = whiteList.includes(ctx.header.origin)

// 對(duì)于預(yù)檢請(qǐng)求,如果沒(méi)有設(shè)置正確的響應(yīng)狀態(tài),瀏覽器會(huì)直接攔截真實(shí)請(qǐng)求,直接報(bào)錯(cuò)提示跨域

// 所以我們可以在這一部分,確定客戶端的請(qǐng)求是否符合我們的要求

if (ctx.method === "OPTIONS") {

if (!pass) return

// 預(yù)檢放行

ctx.status = 204

}

await next();

});

響應(yīng)的狀態(tài)碼是決定預(yù)檢請(qǐng)求是否通過(guò)的關(guān)鍵,返回正常的狀態(tài)碼(通常是204)就能通過(guò)預(yù)檢請(qǐng)求,讓瀏覽器發(fā)出真實(shí)的請(qǐng)求。

在代碼中也可以看出,pass是決定預(yù)檢請(qǐng)求的關(guān)鍵,那在實(shí)際的項(xiàng)目中,還得根據(jù)設(shè)計(jì)去決定通行的具體條件。當(dāng)通過(guò)預(yù)檢請(qǐng)求后,后臺(tái)可以設(shè)置對(duì)應(yīng)的響應(yīng)頭數(shù)據(jù),例如是否允許目標(biāo)源跨域資源共享:

//server.js

app.use(async (ctx, next) => {

console.log('middleware for cors');

// 允許跨域資源共享的白名單

const whiteList = ['http://127.0.0.1:5500']

// 判斷目標(biāo)源是否通行

const pass = whiteList.includes(ctx.header.origin)

// 對(duì)于預(yù)檢請(qǐng)求,如果沒(méi)有設(shè)置正確的響應(yīng)狀態(tài),瀏覽器會(huì)直接攔截真實(shí)請(qǐng)求,直接報(bào)錯(cuò)跨域

// 所以我們可以在這一部分,確定客戶端的請(qǐng)求是否符合我們的要求

if (ctx.method === "OPTIONS") {

if (!pass) return

// 預(yù)檢放行

ctx.status = 204

}

// 允許訪問(wèn)的origin

ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);

// cookie是否允許攜帶

ctx.set("Access-Control-Allow-Credentials", true);

// 允許訪問(wèn)的HTTP方法

ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");

// 哪些請(qǐng)求頭允許通行

ctx.set(

"Access-Control-Allow-Headers",

"X-Requested-With,Content-Type,Accept,Origin"

);

// 暴露給客戶端的響應(yīng)頭信息,在不設(shè)置的情況下,客戶端只能獲取默認(rèn)的響應(yīng)頭,如’content-type‘

ctx.set(

"Access-Control-Expose-Headers",

"With-Requested-Key"

);

// 設(shè)置對(duì)應(yīng)的響應(yīng)頭數(shù)據(jù)

ctx.set(

"With-Requested-Key",

"HW"

);

// 預(yù)檢結(jié)果的緩存時(shí)間,毫秒為單位,F(xiàn)irefox上限是86400-24小時(shí),Chromium(谷歌引擎)上限是7200-2小時(shí)

ctx.set("Access-Control-Max-Age", 0);

await next();

});

其中需要注意兩個(gè)點(diǎn):

關(guān)于Access-Control-Expose-Header

使用CORS時(shí),瀏覽器只允許獲取默認(rèn)的響應(yīng)頭,像上文代碼中的標(biāo)頭With-Requested-Key,即便我們可以通過(guò)瀏覽器的調(diào)試器查看,也無(wú)法通過(guò)代碼去獲取,這時(shí)候就需要后臺(tái)通過(guò)Access-Control-Expose-Header進(jìn)行暴露(后臺(tái)代碼在已在上方統(tǒng)一貼出)。

前端代碼

<body>

<button id="btn"> 請(qǐng)求資源 </button>

</body>

<script>

btn.onclick = function () {

axIOS.post('http://localhost:9527/getMessage', {

firstName: 'Fred',

lastName: 'Flintstone'

})

.then(function (response) {

// 可以在里面查找到暴露出來(lái)的響應(yīng)頭數(shù)據(jù),如’With-Requested-Key‘: "HW"

console.log(response.headers);

})

.catch(function (error) {

console.log(error);

});

}

</script>

關(guān)于Access-Control-Allow-Credentials

使用CORS時(shí),默認(rèn)不攜帶cookie,需要同時(shí)滿足三個(gè)條件,才能在使用CORS時(shí)進(jìn)行cookie的傳遞:

瀏覽器的請(qǐng)求中,設(shè)置withCredentials參數(shù)為true

服務(wù)端設(shè)置標(biāo)頭Access-Control-Allow-Credentials為true

服務(wù)端設(shè)置標(biāo)頭Access-Control-Allow-Origin不為*

我們可以在原生ajax請(qǐng)求中設(shè)置該參數(shù),或者在axios的默認(rèn)配置中設(shè)置該參數(shù):

// 原生ajax

const xhr = new ()

xhr.withCredentials = true

// axios

axios.defaults.withCredentials = true;

Ok,明白CORS的作用,以及明白CORS中的預(yù)檢機(jī)制后,接下來(lái)是了解什么時(shí)機(jī)下會(huì)觸發(fā)預(yù)檢機(jī)制。

CORS中歸納了一系列不會(huì)觸發(fā)預(yù)檢機(jī)制的請(qǐng)求場(chǎng)景,即滿足所有下述條件的情況下,統(tǒng)稱(chēng)為簡(jiǎn)單請(qǐng)求:

使用這三種方法之一:GET HEAD POST

不得人為設(shè)置此集合外的其他首部字段:Accept Accept-Language Content-Language Content-Type

Content-type的值僅限于這三者之一:

text/plain

multipart/form-data

application/x-www/form-urlencoded

請(qǐng)求中,實(shí)例沒(méi)有注冊(cè)任何事件監(jiān)聽(tīng)器,即實(shí)例對(duì)象可以使用.upload屬性進(jìn)行訪問(wèn)

請(qǐng)求中沒(méi)有使用ReadableStream對(duì)象

小結(jié):CORS中主要區(qū)分了簡(jiǎn)單請(qǐng)求和復(fù)雜請(qǐng)求兩種情況,復(fù)雜請(qǐng)求會(huì)觸發(fā)CORS的預(yù)檢機(jī)制。通過(guò)上方的案例,也可以清楚CORS的配置主要是在服務(wù)端,但客戶端也需要知道CORS的使用注意點(diǎn),例如響應(yīng)頭數(shù)據(jù)的獲取以及cookies的攜帶配置,這些知識(shí)應(yīng)該是前后端都需要掌握的技能點(diǎn)。

3.服務(wù)器代理

同源策略主要是限制瀏覽器和服務(wù)器之間的請(qǐng)求,服務(wù)器與服務(wù)器之間并不存在跨域。

我們可以通過(guò)koa2模擬和實(shí)現(xiàn)這種概念:

//前端代碼

<body>

<button id="btn"> 請(qǐng)求資源 </button>

<script>

btn.onclick = function () {

let url = checkUrlProxy('http://localhost:9527/api/getMessage','api')

axios.post(url, {

firstName: 'Fred',

lastName: 'Flintstone'

})

.then(function (response) {

console.log(response);

})

}

// 判斷接口是否攜帶api字段,若是,則更改為代理服務(wù)器對(duì)應(yīng)的域名

function checkUrlProxy(url, proxyFlag){

let proxyServer = 'http://localhost:1005'

let urlArr = [url.split('/')[1],url.split('/')[3]]

if(urlArr.includes(proxyFlag)) {

return `${proxyServer}/${proxyFlag}${url.split(proxyFlag)[1]}`

}

return url

}

//

</script>

</body>

前端的代碼部分,通過(guò)checkUrlProxy函數(shù)簡(jiǎn)單地確定本次請(qǐng)求是否要轉(zhuǎn)向代理服務(wù)器。

后端代碼如下:

//proxyServer.js

let requestFlag = false

let body = ''

app.use(async (ctx, next) => {

// 全放行

if (ctx.method === "OPTIONS") {

ctx.status = 204

requestFlag = false

} else {

requestFlag = true

}

ctx.set("Access-Control-Allow-Origin", "*");

ctx.set("Access-Control-Allow-Credentials", true);

ctx.set("Access-Control-Request-Method", "*");

ctx.set(

"Access-Control-Allow-Headers",

"X-Requested-With,Content-Type,Accept,Origin"

);

ctx.set("Access-Control-Max-Age", 86400);

// 根據(jù)具體情況進(jìn)行修改

ctx.set("Access-Control-Expose-Headers", "With-Requested-Key");

await next();

if(requestFlag) {

ctx.body = body

body = ''

}

});

app.use(async (ctx, next) => {

if (!requestFlag) return

await p4r(ctx)

});

function p4r(ctx) {

return new Promise((res, rej) => {

const proxyRequest = http.request({

host: '127.0.0.1',

port: 9527,

path: ctx.url,

method: ctx.method,

headers: ctx.header

},

serverResponse => {

serverResponse.on('data', chunk => {

body += chunk

})

serverResponse.on('end', () => {

res(body)

})

}

proxyRequest.end()

})

}

app.on('error', (err, ctx) => {

console.error('server error', err, ctx)

});

app.listen(1005, (err) => {

if (err) console.log('服務(wù)器啟動(dòng)失敗');

else console.log('proxy server 1005 running --> ???');

})

//targetServer.js

const data = {val : 123}

// 配合代理服務(wù)器的post路由

router.post('/api/getMessage', (ctx) => {

ctx.body = JSON.stringify(data)

})

// 定義好路由組件的內(nèi)容后進(jìn)行路由注冊(cè)

app.use(router.routes())

app.on('error', (err, ctx) => {

console.error('server error', err, ctx)

});

app.listen(9527, (err) => {

if (err) console.log('服務(wù)器啟動(dòng)失敗');

else console.log('服務(wù)器啟動(dòng)成功');

})

后端代碼主要分兩部分:

代理服務(wù)器(proxyServer),代理服務(wù)器設(shè)置CORS時(shí)不限制通行,在koa2框架中,通過(guò)中間件向目標(biāo)服務(wù)器發(fā)送請(qǐng)求,當(dāng)接收到對(duì)應(yīng)數(shù)據(jù)后,再響應(yīng)給瀏覽器

目標(biāo)服務(wù)器(targetServer),目標(biāo)服務(wù)器不需要做太復(fù)雜的配置,案例中只是將數(shù)據(jù)傳遞給請(qǐng)求方

Ok,我們通過(guò)這個(gè)案例,明確代理服務(wù)器的具體效果,瀏覽器向目標(biāo)服務(wù)器直接請(qǐng)求資源,仍然會(huì)受到同源策略的影響,但通過(guò)代理服務(wù)器向目標(biāo)服務(wù)器請(qǐng)求資源時(shí),卻沒(méi)這種限制。

那在實(shí)際項(xiàng)目中,我們可以通過(guò)腳手架或打包工具的配置文件,簡(jiǎn)潔方便地設(shè)置代理服務(wù)器,無(wú)需自己手寫(xiě)服務(wù)器代碼,拿vue的腳手架為例:

devServer:{

proxy:{

'api':{

target:'127.0.0.1:9527', //目標(biāo)服務(wù)器地址

changeOrigin: true, // 是否允許跨域

pathRewrite: { //是否重寫(xiě)接口

'api':'',

}

}

}

}

在配置的時(shí)候,可以通過(guò)框架的腳手架,或者打包工具確定配置文件,例如一些熟悉的字眼:vue.config.jswebpack.config.jspackage.json(react),更準(zhǔn)確的做法就是直接去對(duì)應(yīng)工具的官方文檔查閱代理服務(wù)器的配置介紹。

總結(jié)

對(duì)于跨域,許多同學(xué)都答得上來(lái)跨域是怎么產(chǎn)生的,以及解決跨域的方案。但在交流過(guò)程中,就總是一兩句就講完讓我覺(jué)得有點(diǎn)可惜。

前后端交互,或者應(yīng)該說(shuō)網(wǎng)絡(luò)協(xié)議,一直都是個(gè)大課題,是只要涉及這一塊的程序員,都應(yīng)該而且有必要學(xué)習(xí)的內(nèi)容。類(lèi)似上文中CORS配置時(shí)前后端要如何配合,以及使用CORS時(shí)前端的注意點(diǎn)都少有人提及。后端是主要的配置方,但不代表這一塊的知識(shí)限于只需后端理解。

了解知識(shí)點(diǎn)的本質(zhì),才能盡量保證在不同的項(xiàng)目場(chǎng)景實(shí)施對(duì)應(yīng)方案。

分享到:
標(biāo)簽:跨域
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定