1.定义:跨域是指从一个域名的网页去请求另外一个域名的资源。好比从www.baidu.com 页面去请求 www.google.com 的资源。可是通常状况下不能这么作,它是由浏览器的同源策略形成的,是浏览器对JavaScript施加的安全限制。跨域的严格一点的定义是:只要 协议,域名,端口有任何一个的不一样,就被看成是跨域javascript
所谓同源是指,域名,协议,端口均相同。这里说的js跨域是指经过js在不一样的域之间进行数据传输或通讯,好比用ajax向一个不一样的域请求数据,或者经过js获取页面中不一样域的框架中(iframe)的数据。php
概念:只要协议、域名、端口有任何一个不一样,都被看成是不一样的域。css
http://www.123.com/index.html 调用 http://www.123.com/server.PHP (非跨域)html
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不一样:123/456,跨域)html5
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不一样:abc/def,跨域)java
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不一样:8080/8081,跨域)jquery
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不一样:http/https,跨域)git
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。github
浏览器执行javascript脚本时,会检查这个脚本属于哪一个页面,若是不是同源页面,就不会被执行。web
对于端口和协议的不一样,只能经过后台来解决。
缘由就是安全问题:若是一个网页能够随意地访问另一个网站的资源,那么就有可能在客户彻底不知情的状况下出现安全问题。好比下面的操做就有安全问题:
既然有安全问题,那为何又要跨域呢? 有时公司内部有多个不一样的子域,好比一个是location.company.com ,而应用是放在app.company.com , 这时想从 app.company.com去访问 location.company.com 的资源就属于跨域。
4、解决跨域问题的方法
CORS(Cross-Origin Resource Sharing
)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS
背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功仍是失败。
服务器端对于CORS
的支持,主要就是经过设置Access-Control-Allow-Origin
来进行的。若是浏览器检测到相应的设置,就能够容许Ajax进行跨域的访问。
只须要在后台中加上响应头来容许域请求!在被请求的Response header中加入如下设置,就能够实现跨域访问了!
//指定容许其余域名访问 'Access-Control-Allow-Origin:*'//或指定域 //响应类型 'Access-Control-Allow-Methods:GET,POST' //响应头设置 'Access-Control-Allow-Headers:x-requested-with,content-type'
JSONP是JSON with Padding(填充式json)的简写,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON,例如:
callback({"name","trigkit4"});
JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。
JSONP的原理:经过script标签引入一个js文件,这个js文件载入成功后会执行咱们在url参数中指定的函数,而且会把咱们须要的json数据做为参数传入。因此jsonp是须要服务器端的页面进行相应的配合的。(即用javascript动态加载一个script文件,同时定义一个callback函数给script执行而已。)
在js中,咱们直接用XMLHttpRequest
请求不一样域上的数据时,是不能够的。可是,在页面上引入不一样域上的js脚本文件倒是能够的,jsonp正是利用这个特性来实现的。 例如:
有个a.html页面,它里面的代码须要利用ajax获取一个不一样域上的json数据,假设这个json数据地址是http://example.com/data.php,那么a.html中的代码就能够这样:
<script type="text/javascript"> function dosomething(jsondata){ //处理得到的json数据 } </script> <script src="http://example.com/data.php?callback=dosomething"></script>
js文件载入成功后会执行咱们在url参数中指定的函数,而且会把咱们须要的json数据做为参数传入。因此jsonp是须要服务器端的页面进行相应的配合的。
<?php $callback = $_GET['callback'];//获得回调函数名 $data = array('a','b','c');//要返回的数据 echo $callback.'('.json_encode($data).')';//输出 ?>
最终,输出结果为:dosomething(['a','b','c']);
若是你的页面使用jquery,那么经过它封装的方法就能很方便的来进行jsonp操做了。
<script type="text/javascript"> $.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){ //处理得到的json数据 }); </script>
jquery
会自动生成一个全局函数来替换callback=?
中的问号,以后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的做用。$.getJSON
方法会自动判断是否跨域,不跨域的话,就调用普通的ajax
方法;跨域的话,则会以异步加载js文件的形式来调用jsonp
的回调函数。
JSONP的优缺点
JSONP的优势是:它不像XMLHttpRequest
对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中均可以运行,不须要XMLHttpRequest或ActiveX的支持;而且在请求完毕后能够经过调用callback的方式回传结果。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种状况,不能解决不一样域的两个页面之间如何进行JavaScript
调用的问题。
CORS和JSONP对比
CORS与JSONP相比,无疑更为先进、方便和可靠。
一、 JSONP只能实现GET请求,而CORS支持全部类型的HTTP请求。 二、 使用CORS,开发者可使用普通的XMLHttpRequest发起请求和得到数据,比起JSONP有更好的错误处理。 三、 JSONP主要被老的浏览器支持,它们每每不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。
浏览器都有一个同源策略,其限制之一就是第一种方法中咱们说的不能经过ajax的方法去请求不一样源中的文档。 它的第二个限制是浏览器中不一样域的框架之间是不能进行js的交互操做的。
不一样的框架之间是能够获取window对象的,但却没法获取相应的属性和方法。好比,有一个页面,它的地址是http://www.example.com/a.html
, 在这个页面里面有一个iframe
,它的src是http://example.com/b.html
, 很显然,这个页面与它里面的iframe
框架是不一样域的,因此咱们是没法经过在页面中书写js代码来获取iframe
中的东西的:
<script type="text/javascript"> function test(){ var iframe = document.getElementById('ifame'); var win = document.contentWindow;//能够获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的 var doc = win.document;//这里获取不到iframe里的document对象 var name = win.name;//这里一样获取不到window对象的name属性 } </script> <iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
这个时候,document.domain
就能够派上用场了,咱们只要把http://www.example.com/a.html
和 http://example.com/b.html
这两个页面的document.domain都设成相同的域名就能够了。但要注意的是,document.domain的设置是有限制的,咱们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com 中某个文档的document.domain 能够设成a.b.example.com、b.example.com 、example.com中的任意一个,可是不能够设成 c.a.b.example.com,由于这是当前域的子域,也不能够设成baidu.com,由于主域已经不相同了。
1.在页面 http://www.example.com/a.html
中设置document.domain
:
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe> <script type="text/javascript"> document.domain = 'example.com';//设置成主域 function test(){ alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象 } </script>
2.在页面 http://example.com/b.html
中也设置document.domain
:
<script type="text/javascript"> document.domain = 'example.com';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同 </script>
修改document.domain
的方法只适用于不一样子域的框架间的交互。
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的全部的页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。
好比:有一个页面a.html,它里面有这样的代码:
再看看b.html页面的代码:
a.html页面载入后3秒,跳转到了b.html页面,结果为:
咱们看到在b.html页面上成功获取到了它的上一个页面a.html给window.name设置的值。若是在以后全部载入的页面都没对window.name进行修改的话,那么全部这些页面获取到的window.name的值都是a.html页面设置的那个值。固然,若是有须要,其中的任何一个页面均可以对window.name的值进行修改。注意,window.name的值只能是字符串的形式,这个字符串的大小最大能容许2M左右甚至更大的一个容量,具体取决于不一样的浏览器,但通常是够用了。
上面的例子中,咱们用到的页面a.html和b.html是处于同一个域的,可是即便a.html与b.html处于不一样的域中,上述结论一样是适用的,这也正是利用window.name进行跨域的原理。
下面就来看一看具体是怎么样经过window.name来跨域获取数据的。仍是举例说明。
好比有一个www.example.com/a.html页面,须要经过a.html页面里的js来获取另外一个位于不一样域上的页面www.cnblogs.com/data.html里的数据。
data.html页面里的代码很简单,就是给当前的window.name设置一个a.html页面想要获得的数据值。data.html里的代码:
那么在a.html页面中,咱们怎么把data.html页面载入进来呢?显然咱们不能直接在a.html页面中经过改变window.location来载入data.html页面,由于咱们想要即便a.html页面不跳转也能获得data.html里的数据。答案就是在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,而后a.html再去获得iframe获取到的数据。
充当中间人的iframe想要获取到data.html的经过window.name设置的数据,只须要把这个iframe的src设为www.cnblogs.com/data.html就好了。而后a.html想要获得iframe所获取到的数据,也就是想要获得iframe的window.name的值,还必须把这个iframe的src设成跟a.html页面同一个域才行,否则根据前面讲的同源策略,a.html是不能访问到iframe里的window.name属性的。这就是整个跨域过程。
看下a.html页面的代码:
上面的代码只是最简单的原理演示代码,你能够对使用js封装上面的过程,好比动态的建立iframe,动态的注册各类事件等等,固然为了安全,获取完数据后,还能够销毁做为代理的iframe。网上也有不少相似的现成代码,有兴趣的能够去找一下。
经过window.name来进行跨域,就是这样子的。
window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可使用它来向其它的window对象发送消息,不管这个window对象是属于同源或不一样源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。
调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,若是不想限定域,可使用通配符 * 。
须要接收消息的window对象,但是经过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。
上面所说的向其余window对象发送消息,其实就是指一个页面有几个框架的那种状况,由于每个框架都有一个window对象。在讨论第二种方法的时候,咱们说过,不一样域的框架间是能够获取到对方的window对象的,并且也可使用window.postMessage这个方法。下面看一个简单的示例,有两个页面
咱们运行a页面后获得的结果:
咱们看到b页面成功的收到了消息。
使用postMessage来跨域传送数据仍是比较直观和方便的,可是缺点是IE六、IE7不支持,因此用不用还得根据实际须要来决定。
web sockets是一种浏览器的API,它的目标是在一个单独的持久链接上提供全双工、双向通讯。(同源策略对web sockets不适用)
web sockets原理:在js建立了web socket以后,会有一个HTTP请求发送到浏览器以发起链接。取得服务器响应后,创建的链接会使用HTTP升级从HTTP协议交换为web sockt协议。
只有在支持web socket协议的服务器上才能正常工做。
var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss socket.send('hello WebSockt'); socket.onmessage = function(event){ var data = event.data; }
一、什么是图像ping:
图像ping是与服务器进行简单、单向的跨域通讯的一种方式,请求的数据是经过查询字符串的形式发送的,而相应能够是任意内容,但一般是像素图或204相应(No Content)。 图像ping有两个主要缺点:首先就是只能发送get请求,其次就是没法访问服务器的响应文本。
二、使用方法:
var img = new Image(); img.onload = img.onerror = function(){ alert("done!"); }; img.src = "https://raw.githubusercontent.com/zhangmengxue/Todo-List/master/me.jpg"; document.body.insertBefore(img,document.body.firstChild);
而后页面上就能够显示我放在个人github上某个地方的照片啦。
与<img>相似的能够跨域内嵌资源的还有:
(1)<script src=""></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。上面jsonp也用到了呢。
(2) <link src="">标签嵌入CSS。因为CSS的松散的语法规则,CSS的跨域须要一个设置正确的Content-Type消息头。不一样浏览器有不一样的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。
(3)<video> 和 <audio>嵌入多媒体资源。
(4)<object>, <embed> 和 <applet>的插件。
(5)@font-face引入的字体。一些浏览器容许跨域字体( cross-origin fonts),一些须要同源字体(same-origin fonts)。
(6) <frame> 和 <iframe>载入的任何资源。站点可使用X-Frame-Options消息头来阻止这种形式的跨域交互。