重要聲明:本文章僅僅代表了作者個(gè)人對(duì)此觀點(diǎn)的理解和表述。讀者請(qǐng)查閱時(shí)持自己的意見(jiàn)進(jìn)行討論。
首先,跨域不是問(wèn)題。是一種安全機(jī)制。 這是你在開(kāi)發(fā)時(shí)、上線前就必須提前考慮到的安全問(wèn)題并且采取合適的手段去避免這個(gè)問(wèn)題帶來(lái)的程序錯(cuò)誤。不過(guò)通常情況下,前端開(kāi)發(fā)的小伙伴們都非常堅(jiān)信后端小伙伴的接口一定已經(jīng)處理好了跨域這個(gè)需求。然而事實(shí)上許多的前端拿到的都是沒(méi)有解決跨域的接口。又出于某種原因不便與后端交涉并且對(duì)方視乎態(tài)度不是很友好。在這種情況下作為前端的小伙伴們心里簡(jiǎn)直一萬(wàn)頭草泥馬飛過(guò)。
不過(guò)現(xiàn)在你不必為之犯困了,哪個(gè)后端要是不協(xié)助處理跨域?qū)е碌囊幌盗袉?wèn)題的話,請(qǐng)將本文直接甩給后臺(tái),臉必須打響。要解決跨域必須由后端來(lái)一起協(xié)同解決,且主要解決工作在后端。
為了能夠更加快速的解決跨域帶來(lái)的問(wèn)題,下面對(duì)跨域進(jìn)行詳細(xì)介紹。
一、跨域是什么
跨域是瀏覽器加載了與當(dāng)前域名、協(xié)議、端口不同另一站點(diǎn)下的資源,這與各大支持JavaScript的瀏覽器的同源策略是違背的。所謂同源策略,它是由Netscape提出的一個(gè)著名的安全策略?,F(xiàn)在所有支持JavaScript 的瀏覽器都會(huì)使用這個(gè)策略。所謂同源是指,域名,協(xié)議,端口相同。
比如說(shuō),下面的幾個(gè)域名是同源的:
- http://example.com/
- http://example.com:80/
- http://example.com/path/file
它們都具有相同的協(xié)議、相同的域名、相同的端口(不指定端口默認(rèn)80)。
而下面幾個(gè)域名是不同源的:
- http://example.com/
- http://example.com:8080/
- http://www.example.com/
- https://example.com:80/
- https://example.com/
- http://example.org/
- http://ietf.org/
它們有不同的協(xié)議或不同的域名或不同的端口,要注意頂級(jí)域名和二級(jí)域名也是認(rèn)為不同的域名。
二、解決跨域?qū)е碌膯?wèn)題
跨域并不會(huì)阻止請(qǐng)求的發(fā)出,也不會(huì)阻止請(qǐng)求的接受,跨域是瀏覽器為了保護(hù)當(dāng)前頁(yè)面,你的請(qǐng)求得到了響應(yīng),瀏覽器不會(huì)把響應(yīng)的數(shù)據(jù)交給頁(yè)面上的回調(diào),取而代之的是去提示你這是一個(gè)跨域數(shù)據(jù)。提示就是一個(gè)報(bào)錯(cuò)提示,就像這樣:
我們知道了瀏覽器是如何處理的了,才能對(duì)癥下藥來(lái)解決這個(gè)問(wèn)題,下面介紹幾種常用的跨域解決方法:
1、CORS,跨域資源共享
這是最靠譜也是非??茖W(xué)的解決方案,通過(guò)上面的截圖我們可以看到,它提示了一個(gè):從某某位置請(qǐng)求的資源被阻擋了,因?yàn)闆](méi)有在響應(yīng)頭里發(fā)現(xiàn):"Access-Control-Allow-Origin"的響應(yīng)頭??吹竭@個(gè)錯(cuò)誤,我們不得不百度一下,這個(gè)Access-Control-Allow-Origin
是個(gè)何方神圣。
通過(guò)Access-Control-Allow-Origin
響應(yīng)頭,就告訴了瀏覽器。如果請(qǐng)求我的資源的頁(yè)面是我這個(gè)響應(yīng)頭里記錄了的"源",則不要攔截此響應(yīng),允許數(shù)據(jù)通行。比如說(shuō)下面示列了一個(gè)場(chǎng)景:
// 從 http://example.com 界面發(fā)出了一個(gè)請(qǐng)求到:http://example2.com,因?yàn)椴煌矗瑢?dǎo)致了跨域。
// 而 http://example2.com 返回了下面的響應(yīng)頭:
Content-Type: application/json;charset=utf-8
Content-Length: 3210
Server: apache
Access-Control-Allow-Origin: http://example.com
由于瀏覽器檢測(cè)到 http://example2.com 的響應(yīng)頭中顯示的寫(xiě)著:Access-Control-Allow-Origin: http://example.com
,也就是,如果請(qǐng)求數(shù)據(jù)的源是 http://example.com 則可以允許訪問(wèn)返回的數(shù)據(jù)。這樣瀏覽器就不會(huì)拋出錯(cuò)誤提示,而是正確的將數(shù)據(jù)交給你的ajax回調(diào)。
在這個(gè)過(guò)程中跨域也存在,但跨域并沒(méi)有導(dǎo)致問(wèn)題了。因?yàn)楹蠖说捻憫?yīng)充分考慮到了某個(gè)頁(yè)面源要使用這個(gè)資源,早就幫對(duì)方做好了跨域資源共享。這才可以順利的進(jìn)行對(duì)接。
所以,要最簡(jiǎn)單解決跨域?qū)е碌膯?wèn)題,只需要后端響應(yīng)時(shí),在響應(yīng)頭里指定允許調(diào)用資源的源就可以了。除了設(shè)定指定的源以外,你還可以直接寫(xiě)一個(gè)*
號(hào),這樣就表示:此數(shù)據(jù)允許被任何其他的源進(jìn)行獲取。
現(xiàn)在,你了解了Access-Control-Allow-Origin
,其實(shí)除了它,還有與之相關(guān)的更多字段,它們也起到了更多的個(gè)性定值效果。下面進(jìn)行了詳細(xì)介紹。
header頭字段 | 含義 | 取值 |
---|---|---|
Access-Control-Allow-Credentials | 響應(yīng)頭表示是否可以將對(duì)請(qǐng)求的響應(yīng)暴露給頁(yè)面。返回true則可以,其他值均不可以。 | true/false |
Access-Control-Allow-Headers | 表示此次請(qǐng)求中可以使用那些header字段 | 符合請(qǐng)求頭規(guī)范的字符串 |
Access-Control-Allow-Methods | 表示此次請(qǐng)求中可以使用那些請(qǐng)求方法 | GET/POST(多個(gè)使用逗號(hào)隔開(kāi)) |
2、使用JSONP方案
當(dāng)服務(wù)端沒(méi)有返回Access-Control-Allow-Origin
這樣的字段時(shí),是否就意味著不能使用此資源了嗎?不!只能說(shuō)不建議使用此資源了。但我們還有另一種辦法,那就是通過(guò)JSONP??吹竭@個(gè)名字,似乎和json有關(guān),說(shuō)有也有,但也可以說(shuō)沒(méi)有,JSONP只是大多數(shù)甚至全部人們對(duì)這種解決辦法的稱呼。
為了更靈活的使用這中解決辦法,就必須要先了解它的實(shí)現(xiàn)原理。我們知道,在頁(yè)面內(nèi)使用ajax加載別的域名下的數(shù)據(jù)時(shí),是會(huì)被跨域阻止的。那有沒(méi)有辦法讓我們的請(qǐng)求不通過(guò)頁(yè)內(nèi)的ajax,而是讓瀏覽器直接走這個(gè)請(qǐng)求?
有!如果你足夠細(xì)心,你會(huì)發(fā)現(xiàn),<script>節(jié)點(diǎn)當(dāng)有一個(gè)src值時(shí),瀏覽器就會(huì)去加載這個(gè)js,然后并執(zhí)行這個(gè)js文件,同樣的,<img>也可以設(shè)置一個(gè)src,瀏覽器會(huì)加載這個(gè)圖片并顯示。那么,其中<script>節(jié)點(diǎn)在獲取到j(luò)s后還會(huì)執(zhí)行,而我們的業(yè)務(wù)邏輯代碼也是執(zhí)行在相同的js環(huán)境下的。我們能不能想辦法,讓我們的請(qǐng)求不通過(guò)ajax,而是通過(guò)給body中追加一個(gè)<script>節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)的src值就是我們希望的要請(qǐng)求的目標(biāo)接口,這樣,服務(wù)器端返回的數(shù)據(jù)不就繞過(guò)這個(gè)跨域限制,將數(shù)據(jù)拿回來(lái)了。
是的,不過(guò)千萬(wàn)要注意,<script>要求你的返回內(nèi)容必須是一段可以執(zhí)行的js,因此你的返回?cái)?shù)據(jù)就必須是一個(gè)可以執(zhí)行的js語(yǔ)句,而不能是隨便一個(gè)字符串。并且還要保證在執(zhí)行js后我們要知道數(shù)據(jù)回來(lái)了。那么綜合這些考慮,我們想到了一個(gè)解決方案:
我們先定義一個(gè)方法:
// 注意這是前端代碼
var datasuccess = function (data) { // TODO}
現(xiàn)在有了這個(gè)方法,我們將服務(wù)器返回的數(shù)據(jù)改成這種格式:
// 注意這是后端代碼
response.getWrite().print("datasuccess({name: \"Jack\", age: 23});");
后端通過(guò)返回一段js,而這段js實(shí)際上就是在執(zhí)行之前定義好了的datasuccess
方法,并且在執(zhí)行的時(shí)候,還把一些數(shù)據(jù)傳入了進(jìn)來(lái)。嘶~~,這是什么啊,這不就正好我們可以在datasuccess
方法里面拿到返回的data數(shù)據(jù)嗎,而且還是在正確的時(shí)機(jī)進(jìn)行執(zhí)行。這樣,數(shù)據(jù)就名正言順的被我們拿到了啊!
它之所以叫JSONP,可能就是因?yàn)閹缀跛泻蠖嗽趯?xiě)返回?cái)?shù)據(jù)的時(shí)候都是將數(shù)據(jù)參數(shù)傳入的一個(gè)json對(duì)象。其實(shí)你可以甚至可以定義多個(gè)參數(shù),每個(gè)參數(shù)的意義用途你也可以自己設(shè)定。
現(xiàn)在來(lái)看看一個(gè)完整的jsonp方法來(lái)進(jìn)行跨域解決的代碼:
// 先定義要執(zhí)行的方法。
var datasuccess =function(data) {
console.log("數(shù)據(jù)已獲?。?, data);
}
// 然后構(gòu)建一個(gè)script節(jié)點(diǎn),
var scriptDom = document.createElement("script");
scriptDom.src = "http://example2.com/?k=jack";
// 將節(jié)點(diǎn)添加到body,瀏覽器就會(huì)立即開(kāi)始請(qǐng)求。當(dāng)請(qǐng)求順利,就會(huì)執(zhí)行 datasuccess 方法
// 在該方法里執(zhí)行獲取到請(qǐng)求數(shù)據(jù)的邏輯。
而通常我們的接搜數(shù)據(jù)的方法名稱并不是一直不變的,而是每次一個(gè)新的,在script節(jié)點(diǎn)中還會(huì)把方法名稱傳上去,讓服務(wù)端知道我們獲取數(shù)據(jù)的方法名,從而順利的完成調(diào)用。
盡管這個(gè)方法很好,但是它只能走GET的請(qǐng)求方法,因?yàn)槊看蝧cript節(jié)點(diǎn)的請(qǐng)求只有GET請(qǐng)求嘛。所以我們使用JSONP的接口,就只有GET方式。
三、VUE提供的代理配置
如果你是VUE項(xiàng)目,那么你在開(kāi)發(fā)時(shí),通常會(huì)配置一個(gè)代理,來(lái)完成跨域問(wèn)題的修復(fù),似乎沒(méi)有后端的事情,但當(dāng)你正式上線你才知道,代理沒(méi)效果了。是的?,F(xiàn)在介紹一下這個(gè)代理干了一件什么事情。
當(dāng)你在開(kāi)發(fā)VUE項(xiàng)目時(shí),就必然會(huì)開(kāi)一個(gè)server去實(shí)時(shí)預(yù)覽你的代碼效果,這是毋庸置疑的。但你要注意,開(kāi)了一個(gè)server,這個(gè)server能做到事情,可不就是單單給你提供預(yù)覽這么簡(jiǎn)單。它還可以進(jìn)行請(qǐng)求轉(zhuǎn)發(fā),實(shí)際上你配置的那些代理,是先會(huì)請(qǐng)求到你的server,你開(kāi)的server檢查到你對(duì)應(yīng)的配置,再請(qǐng)求你配的目標(biāo)地址。這之間發(fā)生了什么,實(shí)際上就是把你的實(shí)際請(qǐng)求轉(zhuǎn)到了你開(kāi)的server里面去請(qǐng)求了,這就不存在什么瀏覽器同源安全的支配了,當(dāng)然也就沒(méi)有了跨域問(wèn)題。
而當(dāng)你上線項(xiàng)目時(shí),如果你的代理配置得不夠優(yōu)雅,或者不夠標(biāo)準(zhǔn),你要小心了,非常有可能你的請(qǐng)求就都會(huì)失敗。最佳的跨域解決方案,無(wú)非就是后端協(xié)助一起解決,單方面可不能達(dá)到完美。
四、總結(jié)
解決方式還有更多各種各樣的,但我認(rèn)為最優(yōu)雅的莫過(guò)于這兩種,因此其他的解決方式可以暫時(shí)不提及,那些方法不僅增加了閱讀復(fù)雜度還增加了維護(hù)成本不建議使用。無(wú)論是采用何種方式,我們都是要讓后端修改代碼,修改header或修改返回?cái)?shù)據(jù)格式,都離不開(kāi)后端的參與,所以遇到跨域問(wèn)題,趕快找后端,一起解決這個(gè)問(wèn)題。