个人对跨域的一些理解,如有不对,请多多指教。
通过XHR实现Ajax通信的一个主要限制,来源与跨域安全策略。默认情况下,XHR对象只能访问与包含它的页面位于同一个域中的资源。
CORS(Cross-Origin Resource Sharing,跨源资源共享)是W3C的一个工作草案,定义了在必须访问跨资源时,浏览器与服务器应该如何沟通。其背后的基本思想是:就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
对于简单的get或post发送的请求,它没有自定义的头部,而主体内容是text/plain。在发送请求时,需要给它附加一个额外的Origin头部,其中包含请求页面的源信息(协议、域名和端口号),以便服务器根据这个头部信息来决定是否给予响应。
小例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>cors_post</title> </head> <body> <div id="box"></div> <script type="text/javascript"> function createXhr(){ if(typeof XMLHttpRequest){ return new XMLHttpRequest(); }else if(typeof ActiveXObject){ return new ActiveXObject("Microsoft.XMLHTTP"); } } var xhr=createXhr(); var data={ name:"liujie", age:"23" }; xhr.onreadystatechange=function(){ if(xhr.readyState==4){ if(xhr.status==200){ //console.log(xhr.responseText); document.getElementById("box").innerHTML=data.name+'---'+data.age; } } } xhr.open("post","http://www.abc.com/21code/cors/post.php",true); xhr.send(data); </script> </body> </html>请求页面post.php:
<?php header('content-type:application:json;charset=utf8'); // 指定允许其他域名访问 //header('Access-Control-Allow-Origin:http://www.example.com'); header('Access-Control-Allow-Origin:*'); // 响应类型 header('Access-Control-Allow-Methods:POST'); // 响应头设置 header('Access-Control-Allow-Headers:x-requested-with,content-type'); $res= array( 'name' => isset($_POST['name'])? $_POST['name'] : '', 'gender' => isset($_POST['gender'])? $_POST['gender'] : '' ); echo json_encode($res); ?>通过http://www.example.com访问这个页面,Origin:http://www.example.com
如果服务器认为这个请求可以接收,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以回发"*")。
比如:Access-Control-Allow-Origin:http://www.example.com
如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。
使用img标签,我们知道,一个网页可以从任何网页中加载图像,不用担心跨域不跨域。
动态创建图像经常用于图像Ping。图像Ping是与服务器进行简单、单向的跨域通信的一种方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或204响应。通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应是什么时候接收到的。来看下面的例子。
<!DOCTYPE html> <html> <head> <title>Image Ping Example</title> <meta charset="utf-8"> </head> <body> <script> var img = new Image(); img.onload = img.onerror = function(){ alert("Done!"); }; img.src = "http://www.example.com/ajax?name=test"; </script> </body> </html>
图像Ping最常用于跟踪用户点页面或动态广告曝光次数。图像Ping有两个主要的缺点,一是只能发送GET请求,二是无法访问服务器的响应文本。因此,图像Ping只能用于浏览器与服务器间的单向通信。
JSONP
:json+padding
(内填充),顾名思义,就是把JSON填充到一个盒子里
原理是:动态插入script
标签,通过script
标签引入一个js
文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json
数据作为参数传入。
由于同源策略的限制,XmlHttpRequest
只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script
标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。
优点是兼容性好,简单易用,支持浏览器与服务器双向通信。缺点是只支持GET请求。
1.在http://example.com/jsonp/目录下有个remote.js文件代码如下:
alert("我是远程服务器上的文件");
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>jsonp01</title> </head> <body> <script type="text/javascript" src="http://example.com/jsonp/remote.js"></script> </body> </html>
2.在jsonp02.html页面定义一个函数,然后在远程remote.js中传入数据进行调用
jsonp02.html页面代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>jsonp02</title> </head> <body> <script type="text/javascript"> //现在我们在jsonp02.html页面定义一个函数,然后在远程remote.js中传入数据进行调用。 var localHandler = function(data){ alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result); }; </script> <script type="text/javascript" src="http://example.com/jsonp/remote.js"></script> </body> </html>
localHandler({"result":"我是远程js带来的数据"});访问jsonp.html页面,成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,并且还接收到了远程js带来的数据。
3.动态生成script标签
jsonp03.html页面的代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>jsonp03</title> </head> <body> <script type="text/javascript"> // 得到学生信息查询结果后的回调函数 var flightHandler = function(data){ alert('你查询的学生信息结果是:姓名:' + data.name+'--'+ '年龄:' + data.age); }; // 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码) var url = "http://example.com/jsonp/remote.js?num=001&callback=flightHandler"; // 创建script标签,设置其属性 var script = document.createElement('script'); script.src=url; // 把script标签加入head,此时调用开始 document.getElementsByTagName('head')[0].appendChild(script); </script> </body> </html>remote.js文件代码如下:
flightHandler({ "num": "001", "name": "lisi", "age": 25 });调用的url中传递了一个num参数,告诉服务器我要查的是001的学生信息,而callback参数则告诉服务器,我的本地回调函数叫做flightHandler,所以请把查询结果传入这个函数中进行调用。我们看到,传递给flightHandler函数的是一个json,它描述了学生的基本信息。运行一下页面,成功弹出提示窗口,jsonp的执行全过程顺利完成!
将子域和主域的document.domain
设为同一个主域.前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain
进行跨域
主域相同的使用document.domain
假设有一个页面,它的地址是http://www.example.com/abc.html,在这个页面里面有一个iframe,它的src是http://example.com/ab.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西。
这时在页面 http://example.com/ab.html 和http://www.example.com/abc.html中设置相同的document.domain,这样我们就可以通过js访问到iframe中的各种属性和对象了。
http://www.example.com/abc.html页面代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>通过修改document.domain来跨子域</title> </head> <body> <iframe src="http://example.com/ab.html" id="iframe" onload="test()"></iframe> <script type="text/javascript"> document.domain="example.com";//设置成主域 function test(){ var iframe=document.getElementById("iframe"); var win=iframe.contentWindow; var doc=win.document; var name=win.name; alert(win); alert(doc); alert(name); } </script> </body> </html>http://example.com/ab.html页面代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>通过修改document.domain来跨子域</title> </head> <body> <script type="text/javascript"> document.domain="example.com";//设置成主域 </script> </body> </html>
window
对象有个name
属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name
的,每个页面对window.name
都有读写的权限,window.name
是持久存在一个窗口载入过的所有页面中的
http://www.example.com/aaa.html页面代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>使用window.name来进行跨域</title> <script type="text/javascript"> function getData(){//iframe载入data.html页面后会执行此函数 var iframe=document.getElementById("iframe"); iframe.onload=function(){//这个时候aaa.html与iframe已经是处于同一源了,可以互相访问 var data=iframe.contentWindow.name;//获取iframe里的window.name,也就是data.html页面给它设置的数据 alert(data);//成功获取到了data.html里的数据 }; iframe.src="a.html";//这里的a.html为随便的一个页面,只要与aaa.html同源就行,目的是让aaa.html能访问到iframe里的东西,设置成about:blank也行 } </script> </head> <body> <iframe style="display:none;" src="http://example.com/data.html" id="iframe" onload="getData()"></iframe> </body> </html>
http://example.com/data.html页面代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>使用window.name来进行跨域</title> </head> <body> <script type="text/javascript"> window.name="我是页面aaa.html想要的数据"; </script> </body> </html>
跨文档消息传送(cross-document messaging),简称XDM,指的是在来自不同域的页面间传递消息。
这个例子就是位于www.example.com域中的页面postmessage.html与位于一个内嵌框架中的www.abc.com域中的页面postmessage2.html通信。
XDM的核心是postMessage()方法,利用这个方法向另外一个地方传递数据。这里另外一个地方指的是包含在当前页面中的<iframe>元素,或者由当前页面弹出的窗口。 postMessage()方法接收两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。第二个参数对保障安全通信非常重要,可以防止浏览器把消息发送到不安全的地方。如果传给postMessage()方法的第二个参数是"*",则表示可以把消息发送给来自任何域的文档。
接收到跨文档消息传送的消息时,会触发window对象的message事件,这个事件是以异步形式触发的
要想接收从其他窗口发过来的消息,必须对窗口对象的message事件进行监听
传给onmessage处理程序的事件对象包含以下三个方面的重要信息:
data:作为postMessage()方法第一个参数传入的字符串数据,可以通过message事件的data属性获取消息的内容
origin:发送消息的文档所在的域,可以通过message事件的origin属性获取消息的发送源
source:发送消息的文档的window对象的代理,可以通过message事件的source属性可以获取消息发送源的窗口对象
Demo
postmessage.html页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>跨文档消息传递</title> </head> <body> <div id="content"></div> <div> <input type="text" size="40" id="msg" placeholder="请输入内容"> <input type="button" id="btn" value="Send to iframe"> </div> <iframe id="inner" width="450" height="300" src="http://www.abc.com/HTML5/postMessage/postmessage2.html"></iframe> <script type="text/javascript" src="../EventUtil.js"></script> <script type="text/javascript"> EventUtil.addHandler(window,'message',function(event){ var content=document.getElementById("content"); content.innerHTML="iframe said:"+event.data; //event.source.postMessage("1页面收到消息","http://www.abc.com"); }); EventUtil.addHandler(document.getElementById("btn"),"click",function(event){ //msg中存放的是要发送给另一个页面的消息 var msg=document.getElementById("msg").value; innerWindow=document.getElementById("inner").contentWindow; if(innerWindow.postMessage){ //*表示可以把消息发送给来自任何域的文档 innerWindow.postMessage(msg,"http://www.abc.com/HTML5/postMessage/postmessage2.html"); }else{ alert("Your browser doesn't support Cross Document Messaging."); } }); </script> </body> </html>postmessage2.html页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>跨文档消息传递</title> </head> <body> <div id="content"></div> <div> <input type="text" id="msg" size="40" placeholder="请输入内容"> <input type="button" id="btn" value="Send to parent"> </div> <script type="text/javascript" src="../EventUtil.js"></script> <script type="text/javascript"> EventUtil.addHandler(window,'message',function(event){ var content=document.getElementById("content"); content.innerHTML="parent said:"+event.data; //event.source.postMessage("2页面收到消息","http://www.example.com"); }); EventUtil.addHandler(document.getElementById("btn"),'click',function(event){ var msg=document.getElementById("msg").value; /*跨文档消息传送的核心是postMessage()方法 使用window对象的postMessage()方法向其他窗口发送消息 这个方法接收两个参数:第一个参数为所发送的消息文本;第二个参数为接收消息的对象窗口的URL地址 可以在URL地址字符串中使用通配符"*"指定全部地址,但是建议使用准确的URL地址 */ //console.log(parent);// Window if(parent.postMessage){ parent.postMessage(msg,"http://www.example.com/HTML5/postMessage/postmessage.html");/*使用通配符"*"指定全部地址*/ }else{ alert("Your browser doesn't support Cross Document Messaging."); } }); </script> </body> </html>
Ajax是一种页面向服务器请求数据的技术,Comet是一种服务器向页面推送数据的技术。Comet能够让信息近乎实时的被推送到页面上,非常适合处理体育比赛和股票报价。
有两种实现Comet的方式:长轮询和流。长轮询是传统轮询(即短轮询)的一个翻版,即浏览器定时向服务器发送请求,看看有没有数据更新。
短轮询:浏览器定时向服务器发送请求,看看有没有数据更新
长轮询把传统轮询颠倒了一下,页面发送一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据后,浏览器关闭连接,随即又发起一个到服务器的新请求。这个过程在页面打开期间一直不断持续。
长轮询:页面发送一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据后,浏览器关闭连接,随即又发起一个到服务器的新请求。
无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。轮询的优势是所有浏览器都支持,因为使用XHR对象和setTimeout()就能实现,你要做的就是决定什么时候发送请求。
第二种流行的Comet方式是HTTP流。流不同于上述的两种轮询,它在页面的整个生命周期中只使用一个HTTP连接。具体来说就是浏览器向服务器发送一个请求,然后服务器保持连接打开,然后周期性的向浏览器发送数据。下面这段php脚本就是采用流实现的服务器中的常见方式:
streaming.php:
<?php $i=0; while(true){ echo "Number is $i";//输出一些数据,然后立即刷新输出缓存 flush(); sleep(10);//等几秒 $i++; } ?>
通过侦听readystatechange事件及检测readyState的值是否为3,就可以利用XHR对象实现HTTP流。随着不断从服务器接收数据,readyState的值就会周期性地变为3,当readyState值变为3时,responseText属性中就会保存接收到的所有数据。此时,就需要比较此前接收到的数据,决定从什么位置开始取得最新的数据。
只要readystatechange事件发生,而且readyState值为3,就对responseText进行分割以取得最新数据。这里的received变量用于记录已经处理了多少个字符,每次readyState值为3时都递增。然后,通过progress回调函数来处理传入的新数据。而当readyState值为4时,则执行finished回调函数,传入响应返回的全部内容。
<!DOCTYPE html> <html> <head> <title>HTTP Streaming Example</title> <meta charset="utf-8"> </head> <body> <script> function createStreamingClient(url, progress, finished){ //三个参数分别是:要连接的URL、在接收到数据时调用的函数以及关闭连接时调用的函数 var xhr = new XMLHttpRequest(), received = 0; xhr.open("get", url, true); xhr.onreadystatechange = function(){ var result; if (xhr.readyState == 3){ //只取得最新数据并调整计数器 result = xhr.responseText.substring(received); received += result.length; //调用progress回调函数 progress(result); } else if (xhr.readyState == 4){ //finished函数是用来关闭连接的 finished(xhr.responseText); } }; xhr.send(null); return xhr; } var client = createStreamingClient("streaming.php", function(data){ alert("Received: " + data); }, function(data){ alert("Done!"); }); </script> </body> </html>
Socket.message=function(event){ var data=event.data; //处理数据 }其他事件: