你應該知道如何跨域
前言
轉眼就是秋招季啦。經歷了幾場筆試面試,屢次被問到關于如何實現跨域。老實說,之前都是紙上談兵,也沒有項目需要跨域,甚至覺得這個東西沒什么意義。直到今天項目中遇到了跨域問題,看了不少資料才理解跨域的普遍性和意義。特寫此篇文章整理自己所得。
轉自個人博客: 關于跨域
什么是跨域
一般來說,如果你在開發中需要進行跨域操作(從一個非同源網站發送請求獲取數據),一般而言,你在瀏覽器控制臺看到的結果為:
XMLHttpRequest cannot load http://external-domain/service. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://my-domain’ is therefore not allowed access.
同源策略
說到跨域就不得不提“同源策略”。
同源策略是Web瀏覽器針對惡意的代碼所進行的措施,為了防止世界被破壞,為了保護世界的和平,Web瀏覽器,采取了同源策略,只允許腳本讀取和所屬文檔來源相同的窗口和文檔的屬性。
那么,怎么判斷文檔來源是否相同呢?很簡單,看三個部分: 協議、主機、端口號。只要其中一個部分不同,則不同源。
跨域的應用場景
- 來自 home.example.com 的文檔里的腳本讀取 developer.example.com載入的文檔的屬性。
- 來自 home.example.com 的文檔里的腳本讀取 text.segmentfault.com載入的文檔的屬性。
如何跨域
設置domain屬性
針對上述應用場景的***種情況,可以設置Document對象的domain屬性。但是設置時使用的字符串必須具有有效的域前綴或者它本身。
PS: domain值中必須有一個點號。
PS: domain不能由松散的變為緊繃的。
- //初始值 "home.example.com"
- document.domain = "example.com"; //OK
- document.domain = "home.example.com"; //NO,不能由松散變緊繃
- document.domain = "example"; //NO,必須有一個點號
- document.domain = "another.com"; //NO, 必須是有效域前綴或其本身
JSONP
JSONP由兩部分組成: 回調函數和數據。
原理:通過動態<script>元素來使用,可以通過src屬性指定一個跨域URL。
- function handler(data){
- console.log(data);
- }
- var script = document.createElement("script");
- script.src = "https://segmentfault.com/json/?callback=handler";
- document.body.insertBefore(script, document.body.firstChild);
除此之外,還可以利用jQuery來實現。
- function jsonCallback(json){
- console.log(json);
- }
- $.ajax({
- url: "http://run.plnkr.co/plunks/v8xyYN64V4nqCshgjKms/data-2.json",
- dataType: "jsonp"
- });
運行結果如下:
某些API(例如Github API)允許你定義一個回調函數,當請求返回時執行該函數。
- function logResults(json){
- console.log(json);
- }
- $.ajax({
- url: "https://api.github.com/users/jeresig",
- dataType: "jsonp",
- jsonpCallback: "logResults"
- });
運行結果如下:
優點:
- 兼容性強。
- 簡單易用,能之間訪問響應文本,支持瀏覽器與服務器之間雙向通信。
不足:
- 只能用GET方法,不能使用POST方法
- 無法判斷請求是否失敗,沒有錯誤處理。
跨域資源共享CORS
需要瀏覽器和服務器同時支持。
原理:使用"Origin:"請求頭和"Access-Control-Allow-Origin"響應頭來擴展HTTP。其實就是利用新的HTTP頭部來進行瀏覽器與服務器之間的溝通。
針對前端代碼而言,變化的地方在于相對路徑需改為絕對路徑。
- //以前的方式
- var xhr = new XMLHttpRequest();
- xhr.open("GET", "/test", true);
- xhr.send();
- //CORS方式
- var xhr = new XMLHttpRequest();
- xhr.open("GET", "http://segmentfault.com/test", true);
- xhr.send();
針對服務器代碼而言,需要設置Access-Control-Allow-Origin,顯式地列出源或使用通配符來匹配所有源。
優點:
- CORS支持所有類型的HTTP請求。
- 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據
不足:
- 不能發送和接收cookie
更新:服務端可以通過設置Access-Control-Allow-Credentials該字段來表示是否允許發送Cookie。發送ajax請求時,需配置withCredentials屬性。(感謝sf小伙伴@lloyd_zhou 指正)
具體可查看 阮一峰大大的博客。
- 不能使用setRequestHeader()設置自定義頭部
- 兼容IE10+
postMessage
postMessge()是HTML5新定義的通信機制。所有主流瀏覽器都已實現。該API定義在Window對象。
- otherWindow.postMessage(message, targetOrigin);
message: 要傳遞的消息。
targetOrigin: 指定目標窗口的源。在發送消息的時候,如果目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那么消息就不會被發送;只有三者完全匹配,消息才會被發送。這個機制用來控制消息可以發送到哪些窗口;
當源匹配時,調用postMessage()方法時,目標窗口的Window對象會觸發一個message事件。在進行監聽事件時,應先判斷origin屬性,忽略來自未知源的消息。
- //<http://example.com:8080>上的腳本:
- var popup = window.open(...popup details...);
- popup.postMessage("The user is 'bob' and the password is 'secret'",
- "https://secure.example.net");
- popup.postMessage("hello there!", "http://example.org");
- function receiveMessage(event)
- {
- if (event.origin !== "http://example.org")
- return;
- // event.source is popup
- // event.data is "hi there yourself! the secret response is: rheeeeet!"【見下面一段代碼可知】
- }
- window.addEventListener("message", receiveMessage, false);
針對上面的腳本進行接受數據的操作:
- /*
- * popup的腳本,運行在<http://example.org>:
- */
- //當postMessage后觸發的監聽事件
- function receiveMessage(event)
- {
- //先判斷源
- if (event.origin !== "http://example.com:8080")
- return;
- // event.source:window.opener
- // event.data:"hello there!"
- event.source.postMessage("hi there yourself! the secret response " +
- "is: rheeeeet!",
- event.origin);
- }
- window.addEventListener("message", receiveMessage, false);
后續
收到了很多小伙伴的建議和指正,不勝感激,我會慢慢豐富這篇文章的內容的。請多多指教~
參考文章