JavaScript 跨域漫游

前言:

  最近在公司做了几个项目都涉及到了iframe,也就是在这些iframe多次嵌套的项目中,我发现之前对iframe的认识还是比较不足的,所以就静下心来,好好整理总结了iframe的相关知识:《Iframe 功能详解》
  在做公司项目的过程中,让我纠结之一的就是iframe的跨域问题,在网上查到的资料,基本都是给个思路,加个DEMO,也没有完整的解决方案。所以这里我结合公司的项目实际需求,从新整理了一下javaScript跨域的相关方法。

  PS:请转载的童鞋,请注明出处 ...

目录:

一、 跨域简介
二、 iframe + domain
三、 iframe + hash
四、 iframe + name
五、 iframe + postMessage
六、 JSONP
七、 CROS
八、 Server 反向代理
九、 WebSocket
十、 flash

 

一. 跨域简介

  “JS 跨域” 其实是一个“伪名词”。跨域实际上是浏览器基于安全考虑做出的“同源策略限制”。
这个策略早在1995年便由 Netscape 公司引入浏览器中,目前所有浏览器都实行这个机制。
  “同源策略”的基本规则是:如果协议(protocol)、端口(port)、主机(host) 等,若有一个不同,浏览器便会限制不同域之间的通信与数据交换。
     以  http://www.example.com  为例:

  域名   是否跨域       原因  
  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本身未指定任何关于自身源的信息,
   所以也并不会跨域。

  同源策略可以很好的保护我们网页不会被随意的篡改,我们的信息不被任意获取,但是在某些情况下也会是我们工作的一个障碍。
比方说,现在公司有两个域名:http://www.example.com 以及 http://www.sample.com。
这两个域名下的页面需要相互进行通信,那么想办法跳过同源限制进行跨域操作,就是我们前端必须掌握的技术。
  同源策略对前端功能的影响主要有以下几点:

1. Cookie、Storage、IndexedDB 等无法读取。
2. DOM 无法操作
3. 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

 

二、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请求。

 

三、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。

 

四、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>

 

五、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>

 

六、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的基础学习 》

 

七、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。

  

八、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 下调取该接口。

 

九、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/

 

十、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 请求安全方面

感谢以上文章的作者们!

如果觉得本文对您有帮助或者您心情好~可以支付宝(左)或微信(右)支持一下,就当给作者赞助包烟钱了 ~~:

posted @ 2016-12-03 19:27  卷柏的花期  阅读(1100)  评论(2编辑  收藏  举报