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