跨域技術(上)包括 同源策略及其限制,什麼是跨域,圖像Ping、JSONP、CORS、WebSocket 等跨域技術。php
所謂同源是指:協議,域名,端口都相同,就是同源;前端
協議,域名,端口號有一個不一樣就是跨域。nginx
跨域並非請求發不出去,請求能發出去,服務端能收到請求並正常返回結果,只是結果被瀏覽器攔截了。 git
<script src="..."></script>
| 標籤嵌入跨域腳本。github
<link rel="stylesheet" href="...">
| 標籤嵌入CSS。web
<img>
| 嵌入圖片。支持的圖片格式包括PNG,JPEG,GIF,BMP,SVG,...ajax
<video>
和 <audio>
| 嵌入多媒體資源。json
<object>
, <embed>
和 <applet>
的插件。segmentfault
<frame>
和 <iframe>
| 載入的任何資源。站點可使用X-Frame-Options消息頭來阻止這種形式的跨域交互。
@font-face
引入的字體 | 一些瀏覽器容許跨域字體(cross-origin fonts),一些須要同源字體(same-origin fonts)。
background: url();
背景連接 | 嵌入的資源可能存在跨域
<link>
、<script>
、<img>
、<frame>
等dom標籤1.原理
onload
和onerror
事件處理程序來肯定是否接收到了響應。2.具體步驟
請求的數據是經過查詢字符串形式發送的,而響應能夠是任意內容,但一般是像素圖或 204 響應。經過圖像 Ping,瀏覽器得不到任何具體的數據,但經過偵聽 load 和 error 事件,它能知道響應是何時接收到的。
let img = new Image(); //建立了一個 Image 的實例
// 將 onload 和 onerror 事件處理程序指定爲同一個函數
img.onload = img.onerror = function(){
alert("Done!");
};
// 請求中發送了一個 name 參數
img.src = "http://www.example.com/test?name=Nicholas";
複製代碼
3.應用場景
圖像 Ping 最經常使用於跟蹤用戶點擊頁面或動態廣告曝光次數。
4.圖像 Ping 的缺點
1.JSONP的做用、組成?
2.JSONP實現跨域訪問的原理及優化
2.1 <script>
標籤的做用
<script>
標籤,同一個界面中多個<script>
標籤中的數據能夠相互訪問<script>
的src屬性導入其它資源,經過src屬性導入其它資源的本質就是將資源拷貝到<script>
標籤中<script>
的src屬性不只能導入本地資源, 還能導入遠程資源<script>
的src屬性沒有同源限制, 因此能夠經過<script>
的src屬性來請求跨域數據2.2 JSONP原理
JSONP 是經過動態<script>
元素來使用的,使用時能夠爲 src 屬性指定一個跨域 URL。這裏的<script>
元素與<img>
元素相似,都有能力不受限制地從其餘域加載資源。由於 JSONP 是有效的 JavaScript代碼,因此在請求完成後,即在 JSONP 響應加載到頁面中之後,就會當即執行。
2.3 JSONP優化
2.3.1 爲何要優化(存在的問題)?
<script>
標籤默認是同步,前面的<script>
標籤沒有加載完數據,後面的<script>
標籤就不會被執行, 因此請求數據的<script>
標籤必須放到後面2.3.2 解決方案:
<script>
標籤,由於JS動態建立的<script>
標籤默認就是異步的, 不用等到前面的標籤加載完就能夠執行後面的<script>
標籤2.3.3 實現方式
原生實現
<script>
//jsonp回調方法,必定要寫在jsonp請求前面
function testjson(txt){
alert(txt);
}
</script>
<script>
//動態建立<script>標籤
let oScript = document.createElement("script");
oScript.src = "http://127.0.0.1:80/jQuery/Ajax/20-jsonp.php?cb=test";
document.body.appendChild(oScript);
</script>
複製代碼
jQuery方式實現
jQuery中的Ajax能夠設置爲請求跨域的數據
dataType: "jsonp"
| 設置請求方式爲jsonp,告訴jQuery須要請求跨域的數據。jsonp: "cb"
| 告訴jQuery服務器在獲取回調函數名稱的時候須要用什麼key來獲取jsonpCallback: "handleCallback"
| 自定義回調函數名,告訴jQuery服務器在獲取回調函數名稱的時候回調函數的名稱是什麼<script>
$.ajax({
url: "http://127.0.0.1:80/jQuery/Ajax/22-jsonp.php",
data:{
"name": "ghk",
"age": 18
},
dataType: "jsonp", // 設置請求方式爲jsonp
jsonp: "cb",
jsonpCallback: "handleCallback", // 自定義回調函數名
success: function (msg) {
console.log(msg);
}
});
</script>
複製代碼
3.JSONP的優缺點
3.1 優勢
與圖像 Ping 相比,它的優勢在於可以直接訪問響應文本,支持在瀏覽器與服務器之間雙向通訊。
3.2 缺點
1.CORS是什麼及原理
一句話總結:CORS是跨域的根本解決方法(處理跨域問題的標準作法),由瀏覽器自動完成。
CORS是一個W3C標準,它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
CORS背後的基本思想:使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,仍是應該失敗。
整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。
所以,實現CORS通訊的關鍵是服務器。只要服務器實現了CORS接口,就能夠跨源通訊。
2.CORS的兩種請求
瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時知足如下兩大條件,就屬於簡單請求。
(1) 請求方法是如下三種方法之一:
(2) HTTP的頭信息不超出如下幾種字段:
凡是不一樣時知足上面兩個條件,就屬於非簡單請求。
瀏覽器對這兩種請求的處理,是不同的。(阮老師這裏有對這兩種請求的詳細解說)
3.實現
普通跨域請求:服務端設置Access-Control-Allow-Origin便可,前端無須設置;若要帶cookie請求,先後端都須要設置。
在響應頭上添加 Access-Control-Allow-Origin 屬性,指定同源策略的地址。同源策略默認地址是網頁的自己。只要瀏覽器檢測到響應頭帶上了CORS,而且容許的源包括了本網站,那麼就不會攔截請求響應。
原生實現
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;// 前端設置是否帶cookie
...
複製代碼
jQuery Ajax
$.ajax({
...
xhrFields: {
withCredentials: true, // 前端設置是否帶cookie
},
crossDomain: true, // 會讓請求頭中包含跨域的額外信息,但不會含cookie
...
});
複製代碼
4.CORS優缺點
CORS要求瀏覽器(>IE10)和服務器的同時支持,是跨域的根本解決方法,由瀏覽器自動完成。 優勢在於功能更增強大支持各類HTTP Method,缺點是兼容性不如JSONP。
同源策略對 WebSocket 不適用,所以能夠經過它打開到任何站點的鏈接。
1.WebSocket的由來
爲了解決服務器向瀏覽器端及時推送消息的需求,提出了一些替代的、變通的解決方案,如iframe、AJAX,基本思路是瀏覽器端及時提交請求,服務器收到請求後進行阻塞,並保持鏈接,等待有消息須要發送時,及時向瀏覽器端進行響應推送。 然而,這些技術都是基於HTTP協議實現。從根本上講,HTTP是半雙工的協議,也就是說,在同一時刻信息只能單向傳輸。瀏覽器端向服務器發送請求(單向),而後服務器響應請求(單向)。這樣,服務器只能被動的接收到請求信息後,再向瀏覽器端響應請求,而沒法主動向客戶端推送信息。
然而,隨着人們對信息的實時性要求愈來愈高(如在線訂票系統、即時通訊系統和股票交易系統等),當服務器數據發生變化的時候,須要主動、實時地向瀏覽器端發送消息,將最新的數據或事件通知給用戶。顯然,HTTP做爲半雙工的通訊協議並不能高效地支持上述需求功能的實現。咱們須要一種高效節能的全雙工通訊機制來保證數據的實時傳輸。所以,WebSocket應運而生。
2.WebSocket的優勢和應用場景
2.1 WebSocket的優勢
WebSocket做爲一種高效的全雙工通訊機制,有以下優勢
2.2 應用場景
經過WebSocket實現的瀏覽器後臺傳輸技術,能夠應用於如多媒體聊天、實時狀態監控、股票行情、基於位置的應用等須要實時刷新的應用場景。
3.WebSocket協議通訊機制
WebSocket協議是獨立的、基於TCP的協議。其本質是先經過HTTP/HTTPS協議進行握手後建立一個用於交換數據的TCP鏈接,此後服務器端與瀏覽器端經過此TCP鏈接進行實時通訊。
圖所示爲WebSocket的通訊模式:
瀏覽器端首先向服務器端發起一個HTTP請求。這個請求包含了一些附加頭信息,是一個升級的HTTP請求。服務器端解析這些附加頭信息後,產生應答信息返回給瀏覽器端。這樣,瀏覽器端和服務器端之間的WebSocket鏈接就創建起來了。雙方能夠經過這個鏈接通道自由地傳輸數據,直到瀏覽器端或服務器端主動關閉該鏈接。
4.WebSocket協議通訊實現的相關技術
4.1 WebSocket構造函數
4.1.1 客戶端要和服務器端創建WebSocket鏈接,只需使用構造函數創建一個WebSocket對象,而且爲這個對象提供鏈接端口的URL地址便可。
let ws = new WebSocket("ws://127.0.0.1:8080/WebSocket/WebSocket")
複製代碼
4.1.2 注意點
URL地址的字符串須要以「ws」或「wss」(加密通訊時使用)做爲開頭。若是URL地址有語法錯誤,那麼構造函數將會拋出異常。
必須給 WebSocket 構造函數傳入絕對 URL。同源策略對 Web Sockets 不適用,所以能夠經過它打開到任何站點的鏈接。至因而否會與某個域中的頁面通訊,則徹底取決於服務器。
4.2 WebSocket的readyState屬性
readyState
屬性返回實例對象的當前狀態,共有四種。
狀態 | 含義 |
---|---|
WebSocket.OPENING (0) | 正在創建鏈接。 |
WebSocket.OPEN (1) | 已經創建鏈接。 |
WebSocket.CLOSING (2) | 正在關閉鏈接。 |
WebSocket.CLOSE (3) | 已經關閉鏈接。 |
4.3 WebSocket事件
open
事件(WebSocket鏈接創建後觸發該事件)
一旦服務器響應了客戶端的WebSocket鏈接請求,open事件就會被觸發。而要監聽open事件,只須要爲其添加回調函數。
ws.onopen = function(){
setConnected(true));
};
複製代碼
message
事件(當客戶端接收到服務器的數據時觸發該事件)
當客戶端接收到服務器發送的消息後,message事件觸發,而且客戶端可調用相應函數對所接收的消息進行處理。
ws.onmessage= function(e){
console.log("接收:"+e.data);
}
複製代碼
error
事件(當通訊過程當中發生錯誤時觸發該事件)
error事件會在WebSocket鏈接出現故障時觸發,用來處理異常事件。
ws.onerror = function(e){
console.log("WebSocket Error: ", e);
};
複製代碼
close
事件(當鏈接關閉時觸發該事件)。
若是接收到error事件,能夠預期很快會觸發close事件。close事件在WebSocket鏈接關閉時觸發,處理程序以下所示:
ws.onclose = function(e){
setConnected(false));
};
複製代碼
一旦鏈接關閉,客戶端和服務器再也不接收或者發送消息。
4.4 WebSocket方法
WebSocket對象有兩個方法即send()和close(),分別用來發送數據和關閉鏈接。
send()
方法ws.send("message");
複製代碼
close()
方法ws.close(code, reason);
複製代碼
能夠在close()方法傳遞兩個可選參數:code(數字型的狀態代碼)和reason(一個文本字符串),用來講明關閉鏈接的緣由。5.WebSocket小結
有沒有發現好用的東西、牛逼的東西總得要有點小缺陷才行,例如兼容問題,否則開發就沒啥意思了(不是
JavaScript 中建立了 WebSocket 以後,會有一個 HTTP 請求發送到瀏覽器以發起鏈接。在取得服務器響應後,創建的鏈接會使用 HTTP 升級從 HTTP 協議交換爲 Web Socket 協議。
也就是說,使用標準的 HTTP 服務器沒法實現 WebSocket ,只有支持這種協議的專門服務器才能正常工做
目前支持WebSocket的服務器有不少,包括了Jetty七、Netty、mod pyWebSocket、Nodejs和Tomcat7等。在雙向通道創建後,服務器端一樣採用事件驅動的架構模式,監聽事件、並觸發相應方法函數來向客戶端推送數據。
本文參考書籍:
《JavaScript高級程序設計》
本文參考連接:
跨域技術(下)包括 iframe標籤的使用 和 document.domain、window.name、location.hash、postMessage等跨域技術。
域名地址的組成
iframe 標籤iframe是一種HTML標記,它會建立包含另一個文檔的內聯框架,經過iframe框架能夠在當前頁面中顯示其餘頁面的信息。
將iframe的src屬性設置爲對另一個頁面的鏈接請求,並在當前頁面中經過JavaScript動態更新iframe的內容,就能夠將服務器端的數據響應到客戶端且而不會出現主頁面一片空白,等待刷新的現象。
而且,僅刷新iframe框架而不是主頁面,也減小了頁面刷新的內容,這在必定程度上提升了頁面刷新速度。
iframe 標籤的使用
1.用iframe
嵌套頁面時,若是父頁面要獲取子頁面裏面的內容,可使用 contentWindow
或者contentDocument
子頁面獲取父頁面使用window.parent
//父窗口
<iframe id="father" src="iframe.html"></iframe>
<script>
console.log(iframe.contentWindow); //獲取子頁面裏面的內容
console.log(iframe.contentDocument);
</script>
//子窗口
<div id="son">內容</div>
<script>
console.log(window.parent); //獲取父頁面內容
</script>
複製代碼
2.頁面間通訊,獲取子頁面的內容、cookie等
//父窗口
<iframe id="father" src="iframe.html"></iframe>
<script>
let iframe = document.getElementById("father");
iframe.onload = function(){
let doc = iframe.contentWindow.document;
console.log(doc.getElementById('son').innerHTML);//'內容'
console.log(document.cookie);//'name=match'
}
</script>
//子窗口
<div id="son">內容</div>
<script>
document.cookie = 'name=match';
</script>
複製代碼
這種方式只適合主域名相同,子域名不一樣的iframe跨域。
經過修改document的domain屬性,咱們能夠在域和子域或者不一樣的子域之間通訊。同域策略認爲域和子域隸屬於不一樣的域,好比www.a.com和sub.a.com是不一樣的域,這時,咱們沒法在www.a.com下的頁面中調用sub.a.com中定義的JavaScript方法。可是當咱們把它們document的domain屬性都修改成a.com,瀏覽器就會認爲它們處於同一個域下,那麼咱們就能夠互相調用對方的method來通訊了。
1.主域相同而二級域名不一樣的狀況下,兩個文件分別加上 document.domain = "example.com"
;而後經過 a.html 文件建立一個 iframe,去控制 iframe 的 window,從而進行交互,這種方法只能解決主域相同而二級域名不一樣的狀況。
2.注意
使用document.domain容許子域安全訪問其父域時,您須要設置document域在父域和子域中具備相同的值。這是必要的,即便這樣作只是將父域設置回其原始值。不然可能會致使權限錯誤。這裏都是a.com。
// 在www.a.com/a.html中:
<script>
document.domain = 'a.com';
let ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
let doc = ifr.contentDocument || ifr.contentWindow.document;
ifr.onload = null;
};
</script>
複製代碼
// 在www.script.a.com/b.html中:
<script>
document.domain ='a.com';
window.data = '傳送的數據:1111';
</script>
複製代碼
window.name
屬性用於獲取/設置窗口的名稱。該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的全部的頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的全部頁面中的。window.name屬性的神奇之處在於name值在不一樣的頁面(甚至不一樣域名)加載後依舊存在(若是沒修改則值不會變化),而且能夠支持很是長的 name 值(2MB)。
當該window的location變化,而後從新加載,它的name屬性能夠依然保持不變。
在控制檯輸入:
頁面跳轉到百度時,name屬性值不變能夠將跨域請求用以下步驟解決:
window.name 的優點在於巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操做。
location.hash就是錨點值,又稱爲片斷標識符(fragment identifier),指的是URL的#號後面的部分。
若是隻是改變片斷標識符,頁面不會從新刷新 ,因此能夠利用hash值來進行數據的傳遞,固然數據量是有限的。
1.假設 github.io 域名下 a.html 和 mouyuming.eu 域名下 b.html 存在跨域請求,那麼利用 location.hash 的一個解決方案以下:
以上步驟中須要注意第二點:如何在 iframe 頁面中修改 父親頁面的 hash 值。因爲在 IE 和 Chrome 下,兩個不一樣域的頁面是不容許 parent.location.hash 這樣賦值的,因此對於這種狀況,咱們須要在父親頁面域名下添加另外一個頁面來實現跨域請求,具體以下:
2.location.bash 方法的優缺點
優勢在於能夠解決域名徹底不一樣的跨域請求,而且能夠實現雙向通信。
而缺點則包括如下幾點:
HTML5 爲了跨域問題,引入了一個全新的 API:跨文檔通訊 API(Cross-document messaging)。這個 API 爲 window 對象新增了一個 window.postMessage 方法,容許跨窗口通訊,不論這兩個窗口是否同源。
圖所示爲postMessage的兼容性:
1.語法格式:otherWindow.postMessage(message, targetOrigin, [transfer]);
參數:
otherWindow
| 其餘窗口的一個引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames。
message
| 將要發送到其餘 window的數據;是具體的信息內容。
targetOrigin
| 接收消息的窗口的源(origin),即"協議 + 域名 + 端口"。也能夠設爲*,表示不限制域名,向全部窗口發送。
若是你明確的知道消息應該發送到哪一個窗口,那麼請始終提供一個有確切值的targetOrigin,而不是*。不提供確切的目標將致使數據泄露到任何對數據感興趣的惡意站點。
2.父窗口和子窗口均可以經過message事件,監聽對方的消息。
message 的屬性有:
data
| 從其餘 window 中傳遞過來的對象。(消息內容)
origin
| 調用 postMessage 時消息發送方窗口的 origin。(消息發向的網址)
source
| 對發送消息的窗口對象的引用。(發送消息的窗口)
//發送頁面
<body>
<iframe id="myIframe" src="http://localhost:3000/user/reg"></iframe>
<input type="button" value="點我" onclick="test()">
<script>
function test() {
let frm = document.getElementById("myIframe");
frm.contentWindow.postMessage("跨域請求信息","http://localhost:3000");
}
</script>
</body>
// 接收頁面
<script>
//接收信息頁面 http://localhost:3000/parent.html
window.addEventListener("message",function (e) {
console.log(e.data);
},false);
</script>
複製代碼
NodeJS中間件代理跨域、nginx反向代理中設置、flash等第三方插件等
本文參考連接