JavaScript 跨域漫遊

前言:javascript

  最近在公司作了幾個項目都涉及到了iframe,也就是在這些iframe屢次嵌套的項目中,我發現以前對iframe的認識仍是比較不足的,因此就靜下心來,好好整理總結了iframe的相關知識:《Iframe 功能詳解》
  在作公司項目的過程當中,讓我糾結之一的就是iframe的跨域問題,在網上查到的資料,基本都是給個思路,加個DEMO,也沒有完整的解決方案。因此這裏我結合公司的項目實際需求,重新整理了一下javaScript跨域的相關方法。php

  PS:請轉載的童鞋,請註明出處 ...html

目錄:前端

1、 跨域簡介
2、 iframe + domain
3、 iframe + hash
4、 iframe + name
5、 iframe + postMessage
6、 JSONP
7、 CROS
8、 Server 反向代理
9、 WebSocket
10、 flashjava

 

一. 跨域簡介

  「JS 跨域」 實際上是一個「僞名詞」。跨域其實是瀏覽器基於安全考慮作出的「同源策略限制」。
這個策略早在1995年便由 Netscape 公司引入瀏覽器中,目前全部瀏覽器都實行這個機制。
  「同源策略」的基本規則是:若是協議(protocol)、端口(port)、主機(host) 等,如有一個不一樣,瀏覽器便會限制不一樣域之間的通訊與數據交換。
     以  http://www.example.com  爲例:jquery

  域名   是否跨域       緣由  
  http://www.example/com/js/b.js                        pathname    
  https://www.example.com protocol
  http://www.example:80.com port
  http://a.example.com host
  http://www.sample.com host
  http://127.0.0.1 host

 

 

 

 

 

 

 

* 瀏覽器只會根據URL進行判斷是否跨域,而不會去判斷兩個域名是否對應一個IP,更或者IP與域名之間的判斷。
* 來自about:blank,javascript:和data:URLs中的內容,繼承了將其載入的文檔所指定的源,由於它們的URL自己未指定任何關於自身源的信息,
   因此也並不會跨域。nginx

  同源策略能夠很好的保護咱們網頁不會被隨意的篡改,咱們的信息不被任意獲取,可是在某些狀況下也會是咱們工做的一個障礙。
比方說,如今公司有兩個域名:http://www.example.com 以及 http://www.sample.com。
這兩個域名下的頁面須要相互進行通訊,那麼想辦法跳過同源限制進行跨域操做,就是咱們前端必須掌握的技術。
  同源策略對前端功能的影響主要有如下幾點:git

1. Cookie、Storage、IndexedDB 等沒法讀取。
2. DOM 沒法操做
3. AJAX沒法請求。web

就目前而言,前端能夠實現跨域的技術,主要有如下幾點:ajax

A. IFRAME
  iframe 的優勢體現於,iframe的src屬性所鏈接的資源不受同源策略影響,而且每一個iframe都是一個獨立的嵌套子窗口。
  a. iframe + domain
  b. iframe + hash
  c. iframe + name
  d. iframe + postmessage
B. 協做
    這裏列舉的技術,不在是單純前端可以實現,而是須要與後端配合。
  e. jsonp
  f. CROS
  g. nginx反向代理
  h. WebSocket
C. 其它
    i. flash

 

2、iframe + domain

   對於兩個域名,若是有相同主域.例如 http://b.example.com 與 http://a.example.com 這兩個域名,有相同的主域 example,在這種狀況下,咱們能夠經過指定 domain 來實現兩個頁面之間的相互通訊。

準備:

----------------------------
 example.com :  A.html
 example.com :  B.html
   在A.html 放入一個iframe 把 B.html 內嵌到 A.html中.
----------------------------

示例:

  A.html - Code:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Document</title>
 6 </head>
 7 <body>
 8     <p>This is A html</p>
 9     <iframe src="http://b.st.cn"></iframe>
10     <button>set B.html BackGround</button>
11 </body>
12 </html>
13 <script>
14 
15     var ifr = document.getElementsByTagName('iframe')[0],
16         btn = document.getElementsByTagName('button')[0];
17 
18     document.domain = 'example.com';
19 
20     load(ifr,function(){
21         btn.onclick=function(){
22             ifr.contentWindow.document.body.style.background = 'red';
23         }
24     });
25 
26 </script>

  B.html - Code:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3     <head>
 4         <meta charset="UTF-8">
 5         <title>Document</title>
 6     </head>
 7     <body>
 8         <p>This is B html</p>
 9         <button>set A.html BackGround</button>
10     </body>
11 </html>
12 <script>
13     var btn = document.getElementsByTagName('button')[0];
14 
15     document.domain = 'example.com';
16 
17     btn.onclick=function(){
18         window.top.document.body.style.background = 'blue';
19     }
20 </script>

PS : 關於load()方法的說明,見:《Iframe 功能詳解》

  對於主域相同而子域不一樣的兩個頁面,設置domain只能夠實現框架之間的交互,若是想進行AJAX請求的話,依然是會有跨域限制。
解決的辦法是在要 XMLHttpRequest 請求的目標頁面下再增長一個代理 proxy.html。而後設置當前頁面與 proxy.html 的 document.domain 。最後再用代理頁面去 AJAX 請求目標頁面。

示例:

A.html - Code:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5 </head>
 6 <body>
 7     <p>This is A html</p>
 8 </body>
 9 </html>
10 <script src="jquery.min.js"></script>
11 <script>
12 
13     document.domain = 'example.com';
14 
15     function crossDomainAjax(url,rqurl,callback,id){
16 
17         var ifr = document.createElement('iframe'),
18             rq = function(){
19                 var $ = ifr.contentWindow.$;
20                 $.get(rqurl,function(data){
21                     callback && callback($(data));
22                 });
23             };
24 
25         ifr.src = url;
26         ifr.id = id;
27 
28         if(window.attachEvent){
29             ifr.attachEvent('onload',rq);
30         }else{
31             ifr.onload= rq;
32         }
33 
34         document.body.appendChild(ifr);
35         return false;
36 
37     }
38 
39     crossDomainAjax('http://b.example.com/proxy.html','http://b.example.cn/index.html',function(data){});
40 
41 </script>

proxy.html - Code :

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Document</title>
 6 </head>
 7 <body>
 8  ...
 9 </body>
10 </html>
11 <script src="jquery.min.js"></script>
12 <script>
13     document.domain = 'example.com';
14 </script>

其中  crossDomainAjax  方法是封裝好的用於相同主域,不一樣子域的AJAX請求。

 

3、iframe + hash

  若是對於域名徹底不一樣的兩個頁面,便沒法再經過設置domain的方式來實現兩個頁面之間的通訊。而使用iframe + hash的組合方式即可以解決不一樣域之間的頁面通訊或數據交換,這是由於改變hash並不會致使頁面刷新,因此能夠利用 location.hash 來進行數據的傳遞。

  iframe+hash實現跨域通訊的邏輯是,在example.com域下有一個 A.html,在 sample.com 下有一個B.html,在A.html 中放入一個iframe ,這個 iframe 的 src 指向的是 B.html,而且在 url後面附加url參數,這樣A.html 內嵌套 B.html的過程就實現了一個請求的發送,B.html中會去讀取發送過來的參數,而後執行相應的處理,處理完成後B.html也會插入一個Iframe,這個iframe嵌入的頁面是與A.html同域的代理頁面。並將處理的結果附加在代理頁面的hash中,最終再由代理頁面將處理的結果傳遞給A.html。

  使用iframe+hash來跨域進行數據傳遞,是有不少弊端的,好比會致使歷史記錄的產生,數據量限制、內容的直接暴露(URL內容是可見的)、傳輸的數據類型.... 因此,這裏我更加推薦下一種要說到的跨域方式 : iframe + name

  具體流程以下圖:

準備:

----------------------------

 example.com : A.html 、proxy.html
 sample.com   : B.html

----------------------------

示例:

A.html - Code :

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>A.html</title>
 6 </head>
 7 <body>
 8     <button>SendMessage</button>
 9     <script>
10         var obtn = document.getElementsByTagName('button')[0];
11 
12         function handle(ifr) {
13             
14             if (window.addEventListener) {
15                 window.onhashchange = function() {
16                     hash = location.hash.split('#')[1];
17                     if(hash){
18                         console.log('接收到了數據:'+hash);
19                         document.body.removeChild(ifr);
20                         this.onhashchange=null;
21                         location.hash = '';
22                     }
23                 };
24 
25             } else {
26                 
27                 (function() {
28                     var timer = null;
29                     setTimeout(function() {
30                         hash = location.hash.split('#')[1];
31                         if (hash) {
32                             console.log('接收到了數據:'+hash);
33                             document.body.removeChild(ifr);
34                             timer = null;
35                             location.hash = '';
36                             return false;
37                         }
38                         timer = setTimeout(arguments.callee, 1000);
39                     }, 0);
40                 }());
41             }
42             document.body.appendChild(ifr);
43         }
44 
45 
46         obtn.onclick = function() {
47             var ifr = document.createElement('iframe'),
48                 iframe = document.getElementById('hashCorssDomain');
49 
50             if(iframe){return false} // 防止併發操做
51             
52             ifr.style.display = "none";
53             ifr.id = 'hashCorssDomain';
54             ifr.src = 'http://sample.com/B.html?openHash=true&hash=msg';
55             handle(ifr);
56 
57         }
58 
59     </script>
60 </body>
61 </html>

B.html - Code 

 

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Document</title>
 6 </head>
 7 <body>
 8     <p>This is B.html</p>
 9 </body>
10 </html>
11 <script>
12     var openHash = getUrl('openHash'),
13         hash = getUrl('hash'),
14         result = '';
15 
16     function getUrl(key, type) {
17         var type = type || '?';
18         var keyArr;
19         if (type == '?') {
20             keyArr = location.search.substring(1).split('&');
21         } else {
22             keyArr = location.hash.substring(1).split('&');
23         }
24         for (var i = 0; i < keyArr.length; i++) {
25             if (key == keyArr[i].split('=')[0]) {
26                 return keyArr[i].split('=')[1];
27             }
28         }
29         return '';
30     }
31 
32     if (openHash && hash) {
33 
34         if (hash == '123') {
35             result = '456';
36         } else {
37             result = 'abc';
38         }
39 
40         try {
41             top.hash = hash + '#' + (~~Math.random() * 1e6) + '=' + result;
42         } catch (e) {
43             var ifr = document.createElement('iframe');
44             ifr.src = 'http://example.com/proxy.html' + '#'+ hash + ~~(Math.random() * 1e6 ) + '=' + result;
45             ifr.style.display = "none";
46             document.body.appendChild(ifr);
47         }
48     }
49 
50 </script>

 

proxy.html - Code :

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5 </head>
 6 <body>
 7     <p>This is Proxy.html</p>
 8 
 9 </body>
10 </html>
11 <script>
12     var result = location.hash.split('#')[1];
13     if(result){
14         top.location.hash = result;
15     }
16 </script>

如下代碼是完成 iframe + hash 跨域的完整示例,這裏爲了後期使用,特地對上面代碼進行封裝

function hashCrossDomain(params) {
	var callback = params.callback || function() {},
		urlparams = params.urlparams || '',
		type = params.type || 'accept',
		href = params.url || '',
		data = '';

	function handle(ifr) {

		if (window.addEventListener) {
			window.onhashchange = function() {
				hash = location.hash.split('#')[1];
				if (hash) {
					callback(hash);
					document.body.removeChild(ifr);
					this.onhashchange = null;
					location.hash = '';
				}
			};

		} else {

			(function() {
				var timer = null;
				setTimeout(function() {
					hash = location.hash.split('#')[1];
					if (hash) {
						callback(hash);
						document.body.removeChild(ifr);
						timer = null;
						location.hash = '';
						return false;
					}
					timer = setTimeout(arguments.callee, 1000);
				}, 0);
			}());
		}
		document.body.appendChild(ifr);
	}

	function getUrl(key, type) {
		var type = type || '?';
		var keyArr;
		if (type == '?') {
			keyArr = location.search.substring(1).split('&');
		} else {
			keyArr = location.hash.substring(1).split('&');
		}
		for (var i = 0; i < keyArr.length; i++) {
			if (key == keyArr[i].split('=')[0]) {
				return keyArr[i].split('=')[1];
			}
		}
		return '';
	}

	if (type === 'send') {

		var ifr = document.createElement('iframe'),
			iframe = document.getElementById('hashCorssDomain');

		if (iframe) return false; // 防止併發操做

		ifr.style.display = "none";
		ifr.id = 'hashCorssDomain';
		ifr.src = href + '?' + urlparams;
		handle(ifr);

	} else {
		openHash = getUrl('openHash');
		data = getUrl('hash');

		if (openHash && data) {
			try {
				top.hash = hash + '#' + (~~Math.random() * 1e6) + '=' + callback(data);
			} catch (e) {
				var ifr = document.createElement('iframe');
				ifr.src = href + '#' + data + ~~(Math.random() * 1e6) + '=' + callback(data);
				ifr.style.display = "none";
				document.body.appendChild(ifr);
			}
		}
	}
}

  使用說明:

  發送方(A.html)調用方法:

document.getElementsByTagName('button')[0].onclick = function() {
	hashCrossDomain({
		'type': 'send',
		'url': 'http://sample.com/B.html',
		'urlparams': 'openHash=true&hash=msg',
		'callback': function(data) {
			console.log('返回的數據' + data);
		}
	})
}

  參數說明:
    type : 用於設置當前頁面屬性,值爲 "send" 表示請求方頁面。值爲 "accept"表示接收方頁面,缺省值表示接收方。
    url : 表示發送目標的url,(這裏即是B.html的地址)
    urlparams : 表示發送的url參數,值爲"openHash=true&hash=msg"格式,其中openHash表示以hash值方式進行跨域,而hash的值則是具體發送的值。
    callback : 響應結果的回調函數。

  接收方(B.html)調用的方法:

hashCrossDomain({
	'url':'http://example.com/proxy.html',
	'callback':function(data){
		if(data == 'msg'){
			return 'abc';
		}else{
			return '123';
		}
	}
});

  參數說明:
    url : 表示發送目標的url,(這裏即是B.html的地址)
    callback : 接收發送的參數,並進行特定的處理,返回處理的結果,該結果會再次做爲url參數發送到代理頁面proxy.html。

 

4、iframe + name

  name 是 window 對象的一個屬性,這個屬性有一個特色,只要在一個窗口(window)的生命週期內,該窗口載入的全部頁面,不論怎麼改變,每一個頁面都能對window.name 進行讀寫,而且支持很是長的name值(~2MB) 例如,在一個窗口內,我A頁面設置的window.name = 'this is A.html',而後經過   location.href = 'B.html'  ,再輸出 window.name 依然是'this is A.html',所以利用這樣一個特性,咱們就能夠實如今不一樣的頁面中進行數據交換或通訊。

  iframe + name 實現的跨域方式與 iframe + hash 原理基本上都是一致的。不過相比之下,window.name 更加實用,由於經過 window.name 不會隨便將數據暴露出來,頁面也不會加入歷史記錄,並且可存儲的內容更加的多。一樣的,經過 window.name 方式跨域的不足之處就是目前JS方面並無好的檢測方法,因此只能經過輪詢的方式監測name值的更改。

  具體流程見下圖:

 

準備:
----------------------------
 example.com : A.html 、 proxy.html
 sample.com : B.html
----------------------------

使用name進行跨域操做的封裝函數:

function nameCorssDomain(params) {

	var callback = params.callback || function() {},
		urlparams = params.urlparams || '',
		type = params.type || 'accept',
		href = params.url || '',
		result = '',
		name = '',
		data = '',
		frame = '',
		ifr = '';

	function handle() {
		(function() {
			var timer = setTimeout(function() {
				if (window.name) {

					iframe = document.getElementById('nameCorssDomain');
					iframe.contentWindow.document.write('');
					iframe.contentWindow.close();
					document.body.removeChild(iframe);
					callback(window.name);
					window.name = '';
					clearTimeout(timer);
					timer = null;

				}
				setTimeout(arguments.callee, 1000);
			}, 0)
		}())
	}

	function getUrl(key, type) {
		var type = type || '?';
		var keyArr;
		if (type == '?') {
			keyArr = location.search.substring(1).split('&');
		} else {
			keyArr = location.hash.substring(1).split('&');
		}
		for (var i = 0; i < keyArr.length; i++) {
			if (key == keyArr[i].split('=')[0]) {
				return keyArr[i].split('=')[1];
			}
		}
		return '';
	}

	if (type === 'send') {

		ifr = document.createElement('iframe');
		ifr.style.display = 'none';
		ifr.id = 'nameCorssDomain';

		if (window.attachEvent) {
			ifr.attachEvent('onload', function() {
				handle();
			});
		} else {
			ifr.onload = function() {
				handle();
			}
		}

		ifr.src = href + '?' + urlparams;
		document.body.appendChild(ifr);
	} else {
		name = getUrl('openName');
		data = getUrl('name');
		name && data ? location.href = href + '?' + callback(data) : '';
	}
}

使用格式:

nameCorssDomain({
	'type':'send',
	'url':'http://sample.com/B.html',
	'data':'openName=true&name=123',
	'callback':function(){}
});

參數說明:
  type : 用於指定當前頁面是發送請求頁面(send) 仍是 接收請求處理頁面(accept)。
  url : 用於指定連接到的目標頁面。對於請求頁面來講,url是接收請求處理頁面,對於接收請求處理頁面而言,url則是代理頁面proxy.html。
  urlparams : 表示發送的url參數,值爲"openName=true&name=123"格式,其中openName表示以name值方式進行跨域,而name屬性的值則是具體發送的值
  data : 發送的url數據參數,其中openName = true表達開啓 name跨域操做,而name = 123,則是具體傳輸的數據。
  callback : 處理結果的回調函數。

具體示例:

A.html - Code:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5 </head>
 6 <body>
 7     <button>Send Message</button>
 8 </body>
 9 </html>    
10 <script>
11 var oBtn = document.getElementsByTagName('button')[0];
12 
13 oBtn.onclick=function(){
14     nameCorssDomain({
15         'type':'send',
16         'url':'http://sample.com/B.html',
17         'urlparams':'openName=true&name=123',
18         'callback':function(data){
19             console.log(data);
20         }
21     });
22 }
23 
24 </script>

B.html - Code

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5 </head>
 6 <body>
 7     <script>
 8 
 9     nameCorssDomain({
10         'url':'http://example.com/proxy.html',
11         'callback':function(data){
12             if(data == '123456'){
13                 return '789';
14             }else{
15                 return 'abcdef';
16             }
17         }
18     });
19 
20     </script>
21 </body>
22 </html>

Proxy.html - Code:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5 </head>
 6 <body>
 7     <p>This is Proxy.html</p>
 8 
 9 </body>
10 </html>
11 <script>
12     var result = location.search.split('?')[1];
13     if(result){
14         top.name = result;
15     }
16 </script>

 

5、iframe + postMessage

  HTML5引入了一個跨文檔消息傳輸(Cross-document messaging)的API。
  這個API爲window對象新增了個postMessage方法,可使用它來向其它的window對象(窗口)發送消息,不管這個window對象是屬於同源或不一樣源。
  同時這個API也爲window新增了一個onmessage事件,用於接收其它窗口發來的消息內容。
  須要注意的是,這個消息內容只能爲字符串,可是能夠設置爲json格式的數據。
  目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。
  詳細支持狀況以下:

  發送消息格式:

otherWindow.postMessage(message, targetOrigin)

    功能:向指定的窗口 otherWindow 發送跨域文檔消息
    參數
      otherWindow: 對接收信息頁面的window的引用。能夠是頁面中iframe的contentWindow屬性;window.open的返回值;經過name或下標從window.frames取到的值。
      message: 所要發送的數據,string類型。
      targetOrigin: 用於限定指定域中的otherWindow,「*」表示不做限制,常規值是一個url。

  接收消息格式:

otherwindow.onmessage=function(event){
	// 經常使用的event屬性。
	//event.source:發送消息的窗口
	//event.origin: 消息發向的網址
	//event.data: 消息內容
};

準備:

----------------------------
 example.com : A.html
 sample.com : B.html
----------------------------

示例:

A.html - Code:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5 </head>
 6 <body>
 7     <iframe src="http://hd.qy.iwgame.test/B.html"  frameborder="0"></iframe>
 8     <script>
 9         var ifr = document.getElementsByTagName('iframe')[0];
10         ifr.onload=function(){
11             window.frames[0].postMessage('msg','http://hd.qy.iwgame.test');
12         }
13     </script>
14 </body>
15 </html>

B.html - Code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script>
        window.onmessage=function(e){
            if(e.origin === 'http://hd.tzj.iwgame.test'){    // 規定只接收指定的窗口消息。
                document.body.style.background = 'red';
            }
        }
    </script>
</body>
</html>

 

6、JSONP

  JSONP(JavaScript Object Notaction With Padding) 就是採用JSON格式表示數據並由雙方開發人員相互約定的一種數據傳輸方法,該協議並非一種公開標準的協議。只是一個相互約定的使用方法。
  JSONP主要是利用HTML - script標籤能夠跨域的特性來解決跨域數據的傳輸與交換。(實際上含有src屬性的HTML標記,都是能夠跨域的)。
  JSONP的核心思路就是經過script標籤去請求一個後臺服務頁面的地址。而後在該後臺頁面中,會調用前端HTML頁面中的一個事先定義好的函數,並將處理的結果做爲函數的參數一併傳輸過去。對於script標籤而言,它不論src指向的url是一個*.js的文件,仍是其餘格式的文件,例如.php或者.jsp等,只要這個url指向的文件能返回一個符合JavaScript語法與格式的字符串便可。

  其工做流程如圖所示:

  

   >>> 更多關於JSONP的相關知識以及封裝方法,請參考個人另外一篇博客:《從 AJAX 到 JSONP的基礎學習 》

 

7、CROS

  CORS(Cross-Origin Resource Sharin) 即跨源資源分享。它已經被W3C標準化。
  CORS是AJAX實現跨域請求的根本解決方案,它容許一個域上的頁面向另外一個域提交跨域請求。而實現這個功能很是簡單,只須要服務端設置響應頭便可。
  CROS於JSONP相比,前者只能發送GET請求,而CORS容許任何類型的請求。可是CORS也有其不足之處,我我的認爲主要有兩點:
    1. CROS 帶來的潛在的安全性問題。
    2. CORS須要瀏覽器和服務器同時支持。

  CORS在瀏覽器中的兼容性見下圖:

    

具體示例:

準備:

----------------------------
 example.com : getName.php
 sample.com : index.html
----------------------------

如今在兩個不一樣的域名中,分別有一個php文件,和一個html文件,如今咱們要實現的需求是,在sample.com域下的index.html 去 AJAX 請求example.com域下的php文件。

index.html - Code - 

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Document</title>
 6 </head>
 7 <body>
 8     <button>click</button>
 9     <script src=".../jquery.min.js"></script>
10     <script>
11         var oBtn = document.getElementsByTagName('button')[0];
12         oBtn.onclick=function(){
13             $.ajax({
14                 type:'post',
15                 url:'http://example.com/getName.php',
16                 data:{'name':'shenguotao'},
17                 success:function(data){
18                     alert(data);
19                 }
20             });
21         }
22     </script>
23 </body>
24 </html>

getName.php - Code - 

1 <?php 
2     header("Access-Control-Allow-Origin:*");
3     $name = $_POST['name'];
4     echo $name;
5 ?>

運行結果見下圖:

  在上面的例子中,後端只要設置了響應頭  header("Access-Control-Allow-Origin:*")  ,就表示該接口能夠被任意域進行請求。

  在上面的例子中,Origin字段用來講明,本次請求來自哪一個源。服務器根據這個值,決定是否贊成此次請求。若是Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。這裏用"*"表示匹配任意的 "源",若是隻匹配特定的源,那麼將 "*" 改爲特定域的地址便可。

  若是是java環境能夠這樣設置: response.setHeader("Access-Control-Allow-Origin", "*") 。

  須要注意的是在CORS請求中,默認是不發送Cookie和HTTP認證信息的。若是要把Cookie發到服務器,一方面要服務器贊成並設定Credentials字段。

header("Access-Control-Allow-Credentials: true");

另外一方面,開發者必須在 AJAX 請求中打開withCredentials屬性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

若是是AJAX,則:

 $.ajax({
   type: "POST",
   url: "http://xxx.com/api/test",
   dataType: 'jsonp',
   xhrFields: {
      withCredentials: true
   },
   crossDomain: true,
   success:function(){

	},
	error:function(){

	}
});

  須要注意的是,若是要發送Cookie,Access-Control-Allow-Origin就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。

  

8、Server 反向代理

  反向代理實際上就是欺騙瀏覽器,明的告訴瀏覽器這個後臺接口是從同域下請求到的,但背後web服務會將後臺接口實際所在的域與瀏覽器當前訪問的域作了某種相似於綁定的操做。
  代理實現跨域的本質就是同源策略只是瀏覽器單方面的限制。
  這裏我就以nginx爲例。

nginx配置參數以下:

server {
	listen       80;
	server_name  example.com;
	index        index.htm index.html;
	location /htmlrpt{
		proxy_pass      sample.com/htmlrpt;	// 若是是 htmlrpt 目錄下的資源,則轉向 sample.com/htmlrpt 這個域名下請求。
		proxy_redirect  off;
		proxy_set_header        Host            $host;
		proxy_set_header        X-Real-IP       $remote_addr;
		proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
	}
	location /{
		root 	D:\\workspace\\git\\src\\main\htmlrpt;
	}
	
}

經過以上設置,咱們在 example.com 域下請求 example.com/htmlrpt/index.php ,實際上web服務會從sample.com/htmlrpt 下調取該接口。

 

9、WebSocket

  WebSocket 是HTML5的一種新技術。它實現了瀏覽器與服務器全雙工即時通訊(full-duplex)。在過去若是想實現一樣的即時通訊,通常都是以輪詢的方式,好比每隔1秒鐘,發送一次http請求,而後由服務器返回最新的數據給客戶端瀏覽器,這種不斷輪詢的方法明顯的缺點就是,須要不斷的向服務器發送請求,而且每次都要攜帶很大的request頭信息,而實際的數據,可能只是很小的一部分。

  WebSocket連線過程當中,須要經過瀏覽器發出WebSocket連線請求,而後服務器發出迴應,這個過程一般稱爲。「握手」 (handshaking)在 WebSocket 方式下,瀏覽器和服務器只須要作一個握手的動做,而後,瀏覽器和服務器之間就造成了一條快速通道。二者之間就能夠直接互相傳送數據。使用WebSocket,爲咱們實現即時服務帶來了兩大好處:

    1. Header
      互相溝通的Header是很小的-大概只有 2 Bytes
    2. Server Push
      服務器的推送,服務器再也不被動的接收到瀏覽器的request以後才返回數據,而是在有新數據時就主動推送給瀏覽器。

  在WebSocket的消息頭中,也有一個與CROS含義相同的頭字段,即:Sec-WebSocket-Origin: null,它用於設置指定的域才能與服務器進行通訊,而咱們前端就是利用WebSocket這種能夠與服務器通訊的功能來實現跨域!固然經過這種方式跨域,有點像大炮打蚊子通常,可是未嘗不是一種方案!

  開發者若是想使用WebSocket須要知道兩點狀況,一是瀏覽器是否支持WebSocket API , 二是後臺服務器是否部署了WebSocket服務。這二者缺一不可。前端對於WebSocket的操做,也就如下幾個方面:

    · 開啓與關閉WebSocket連接
    · 發送與接收消息
    · 錯誤處理

前端示例代碼:

if('WebSocket' in window){

	var connection = new WebSocket('ws://example.com');	

	/* 建立一個WebSocket對象的實例。並與指定WebSocket服務器進行鏈接,而這個鏈接的過程就是握手的過程 
	   此時 connection 對象上有一個 readyState屬性,用於表示當前鏈接的狀態。
	   readyState 狀態值:
		   	0: 正在鏈接
		   	1: 鏈接成功
		   	2: 正在關閉
		   	3: 鏈接關閉
	*/

	function open(){
		console.log('服務器與WebSocket服務連接成功');
	}

	function close(){
		console.log('服務器與WebSocket服務連接關閉');
	}

	/* 當readyState屬性的值爲1,便會觸發 connection對象的open事件
	   當readyState屬性的值爲2,便會觸發 connection對象的close事件,其中若值爲3則表示關閉成功
	*/
	connection.onopen = open();
	connection.onclose = close();

	/* 鏈接成功後,能夠經過 send 方法發送消息 */
	connection.send('hellow WebSocket!'); 

	/* 客戶端接收到服務端發送過來的消息後,WebSocket 便會觸發message事件 */
	connection.onmessage=function(e){
		console.log(e.data);
	}

	/* 若是WebSocket出現錯誤 , 便會觸發error事件*/
	connection.onerror = function(e){
		console.log(e.data);
	}

}

  這裏給一個webSocket在線測試網站,有興趣的童鞋能夠去看看 http://www.blue-zero.com/WebSocket/

 

10、flash

  使用flash方式進行跨域,其原理就是將上述iframe方式中用到的代理頁面(proxy.html)替換成flash而已。
相比於瀏覽器 flash 有着本身的一套安全策略。對於flash而言只要被請求域的根目錄下有一個crossdomain.xml的文件,而且其中規定了當前請求域是被容許狀態,那麼flash即可以請求指定的資源。

  加上javaScript代碼是能夠與flash代碼進行相互調用,因此咱們就能夠用js發起請求,而後讓flash代理請求,並在被請求「域」的根目錄下放置好crossDomain.xml文件,最終flash完成請求得到結果數據,再經過調用頁面的JS回調函數方式將結果發送到頁面上。

  具體流程如圖所示:

  

具體示例:

準備:

----------------------------
  example.com :  index.php
            crossDomain.xml

  sample.com :  ajaxcdr.js
           ajaxcdr.swf
           index.html
----------------------------

這裏咱們直接使用封裝好的flash跨域組件 ajaxcdr。其中ajaxcdr.js 能夠幫助咱們生成flash,而且向flash中發送請求。而ajaxcdr.swf則能夠代理咱們的請求

index.php - Code -

1 <?php
2     header("Cache-Control: no-cache, must-revalidate");
3     $name = $_POST['name'];
4     echo $name; 
5 ?>

crossDomain.xml - Code -

1 <?xml version="1.0" encoding="UTF-8"?>
2     <cross-domain-policy>
3     <allow-access-from domain="*"/>
4 </cross-domain-policy>

index.html - Code -

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5 </head>
 6 <body>
 7     <button>send Request</button>
 8 </body>
 9 </html>
10 <script src="ajaxcdr.js"></script>
11 <script>
12     var button = document.getElementsByTagName('button')[0];
13     button.onclick=function(){
14         flashCrossDomain('http://example/index.php','POST','name=flash-crossdomain');
15     };
16 </script>

 

 打包下載 ajaxcdr 組件。

 


 總結: JavaScript進行跨域的技術,總結到這裏也就結束了,總的來講,每種技術都有它的優點以及它的不足,在使用上,我認爲仍是根據需求去選擇爲好。可是我我的認爲,若是是移動端使用跨域技術,我更加推崇 CROS方式,若是是PC端不考慮低版本瀏覽器的話,我建議使用postMessage,若是考慮低版本的話,我認爲jsonp,iframe + name方式值得使用。


 

參考:
  http://www.cnblogs.com/jingwhale/p/4669050.html 
  http://www.ttlsa.com/web/cross-domain-solutions/
  http://www.ruanyifeng.com/blog/2016/04/cors.html // 對CROS方式講解的很是詳細,感受更加適合後端讀。
  http://javascript.ruanyifeng.com/bom/same-origin.html#toc13 //各類跨域技術都有講解到。

推薦閱讀:
  http://justcoding.iteye.com/blog/1366109 //AJAX 請求安全方面

感謝以上文章的做者們!

若是以爲本文對您有幫助或者您心情好~能夠支付寶(左)或微信(右)支持一下,就當給做者贊助包煙錢了 ~~:

相關文章
相關標籤/搜索