首页 > 代码库 > 跨域问题相关知识详解(原生js和jquery两种方法实现jsonp跨域)

跨域问题相关知识详解(原生js和jquery两种方法实现jsonp跨域)

1、同源策略

同源策略(Same origin policy),它是由Netscape提出的一个著名的安全策略。同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现,现在所有支持JavaScript 的浏览器都会使用这个策略。

所谓同源,就是指两个页面具有相同的协议,主机(也常说域名),端口,三个要素缺一不可。

所谓同源策略,指的是浏览器对不同源的脚本或者文本的访问方式进行的限制。即a.com 域名下的js无法操作b.com或是c.com域名下的对象。详细见下表:

URL1URL2说明是否允许通信
http://www.foo.com/js/a.js http://www.foo.com/js/b.js 协议、域名、端口都相同 允许
http://www.foo.com/js/a.js http://www.foo.com:8888/js/b.js 协议、域名相同,端口不同 不允许
https://www.foo.com/js/a.js http://www.foo.com/js/b.js 主机、域名相同,协议不同 不允许
http://www.foo.com/js/a.js http://www.bar.com/js/b.js 协议、端口相同,域名不同 不允许
http://www.foo.com/js/a.js http://foo.com/js/b.js 协议、端口相同,主域名相同,子域名不同 不允许

 

URL说明是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同文件夹 允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口 不允许
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同协议 不允许
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名对应ip 不允许
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同 不允许
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名 不允许
eg:当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
特别注意两点:
第一,如果是协议和端口造成的跨域问题“前台”是无能为力的;
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。

    前端所说的跨域一般指“前台”处理跨域的办法,后台proxy这种方案牵涉到后台配置,有兴趣的可以参考yahoo的这篇文章:《JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls》

同源策略限制了不同源之间的交互,可我们平时文件中引用其他域名的js文件,css文件,图片文件为何没受到限制呢?同源策略限制的不同源之间的交互主要针对的是js中的XMLHttpRequest等请求,下面这些情况是完全不受同源策略限制的:

  • 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。链接就不用说了,导航网站上的链接都是链接到其他站点的。而你在域名www.foo.com下面提交一个表单到www.bar.com是完全可以的。
  • 跨域资源嵌入是允许的,当然,浏览器限制了Javascript不能读写加载的内容。如前面提到的嵌入的<script src="http://www.mamicode.com/..."></script>,<img>,<link>,<iframe>等。当然,如果要阻止iframe嵌入我们网站的资源(页面或者js等),我们可以在web服务器加上一个X-Frame-Options DENY头部来限制。nginx就可以这样设置add_header X-Frame-Options DENY;

互联网的许多网站之间图片相互盗链,A网站网页的img.src直接链接到B网站的图片地址,就是因为这个原因:<img>的src(获取图片),<link>的href(获取css),<script>的src(获取javascript)这三个都不符合同源策略,它们可以跨域获取数据。因此,你可以直接从一些cdn上获取jQuery,并且你网站上的图片也随时可能被别人盗用。

而我们的第一种跨域方法jsonp,就是因为<script>的src不符合同源策略而来的。

2、跨域问题-JSONP 

Asynchronous JavaScript and XML (Ajax ) 是驱动新一代 Web 站点(流行术语为 Web 2.0 站点)的关键技术。Ajax 允许在不干扰 Web 应用程序的显示和行为的情况下在后台进行数据检索。使用 XMLHttpRequest 函数获取数据,它是一种 API,允许客户端 JavaScript 通过 HTTP 连接到远程服务器。Ajax 也是许多 mashup 的驱动力,它可将来自多个地方的内容集成为单一Web 应用程序。

不过,由于受到浏览器的限制,该方法不允许跨域通信。

说到AJAX首先要思考的两个问题,第一个是AJAX以何种格式来交换数据?第二个是跨域的需求如何解决?这两个问题目前都有不同的解决方案,比如数据可以用自定义字符串或者用XML来描述,跨域可以通过服务器端代理来解决。但到目前为止最被推崇或者说首选的方案还是用JSON来传数据,靠JSONP来跨域。

JSON(JavaScript Object Notation)和JSONP(JSON with Padding)虽然只有一个字母的差别,但其实他们根本不是一回事儿:JSON是一种数据交换格式,而JSONP是一种依靠开发人员的聪明才智创造出的一种非官方跨域数据交互协议。我们拿最近比较火的谍战片来打个比方,JSON是地下党们用来书写和交换情报的“暗号”,而JSONP则是把用暗号书写的情报传递给自己同志时使用的接头方式。看到没?一个是描述信息的格式,一个是信息传递双方约定的方法。

2.1 JSON

JSON:javaScript对象表示法(JavaScript Object Notation)

JSON is a subset of the object literal notation of JavaScript. Since JSON is a subset of JavaScript, it can be used in the language with no muss or fuss.

 JSON是一种轻量级的数据交换格式。(json.org)

JSON是存储和交换文本信息的语法,类似XML。它采用键值对的方式来组织,易于人们阅读和编写,同时也易于机器解析和生成。JSON是独立于语言的,也就是说不管什么语言,都可以解析json,只需要按照json的规则来就行。

JSON语法规则:JSON能够以非常简单的方式来描述数据结构,XML能做的它都能做,因此在跨平台方面两者完全不分伯仲。

(1)JSON只有两种数据类型描述符,大括号{}和方括号[],其余英文冒号:是映射符,英文逗号,是分隔符,英文双引号""是定义符。

(2)大括号{}用来描述一组“不同类型的无序键值对集合”(每个键值对可以理解为OOP的属性描述),方括号[]用来描述一组“相同类型的有序数据集合”(可对应OOP的数组)。

(3)上述两种集合中若有多个子项,则通过英文逗号,进行分隔。

(4)键值对以英文冒号:进行分隔,并且建议键名都加上英文双引号"",以便于不同语言的解析。

(5)JSON内部常用数据类型有字符串、数字、布尔、日期、null 等,字符串必须用双引号引起来,其余的都不用,日期类型比较特殊,建议如果客户端没有按日期排序功能需求的话,那么把日期时间直接作为字符串传递就好,可以省去很多麻烦。

json解析的方法有两种:eval()和parse()方法。————建议尽量使用JSON.parse方法来解析json里的字符串。

JSON和XML比较:

(1)JSON的长度和XML格式比起来很短小;

(2)JSON读写的速度更快;

(3)JSON可以使用JavaScript内建的方法直接进行解析,转换成JavaScipt对象,非常方便

JSON的优点:

(1)基于纯文本,跨平台传递极其简单;

(2)Javascript原生支持,后台语言几乎全部支持;

(3)轻量级数据格式,占用字符数量极少,特别适合互联网传递;

(4)可读性强,容易编写和解析;

JSON的缺点:

(1)JSON在服务端语言的支持不像XML那么广泛,不过JSON.org上提供很多语言的库。

(2)如果你使用eval()来解析的话,会容易出现安全问题。

尽管如此,JSON的优点还是很明显的。他是Ajax数据交互的很理想的数据格式。

2.2 JSONP

JSONP:JSON with Padding(填充式 JSON 或参数式 JSON)JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)

2.2.1 JSONP是怎么产生的?

  1、一个众所周知的问题,由于同源策略,Ajax直接请求普通文件存在跨域无权限访问的问题,甭管你是静态页面、动态网页、web服务、WCF,只要是跨域请求,一律不准;

  2、不过我们又发现,Web页面上调用js文件时则不受是否跨域的影响(不仅如此,我们还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如<script>、<img>、<iframe>);

  3、于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、属于未来的HTML5之Websocket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;

  4、恰巧我们已经知道有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被js原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据;

  5、这样子解决方案就呼之欲出了,web客户端通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件(一般以JSON为后缀),显而易见,服务器之所以要动态生成JSON文件,目的就在于把客户端需要的数据装入进去。

  6、客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但其实并不一样。

  7、为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

一句话总结:由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。

 2.2.2 JSONP的实现原理

JSONP借助了script标签节点跨域访问/获取的特性。JSONP实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的src 不受同源策略约束来跨域获取数据。

JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。

a域名去声明一个方法,b域名去调用这个方法,通过script标签可以向不同域名提交http请求。

一、原生js实现jsonp

 1.最简单的一种,客户端(a域名)的html文件

<!doctype html>
<html lang="en">
<head></head>
<body>
    <script type="text/javascript">
        function jsonpCallback(result) {   
            for(var i in result) {  
                console.log(i+":"+result[i]);//循环输出result的元素  
            }  
        }  
    </script>
    <script type="text/javascript" src="http://180.167.10.100/update/index.php?callback=jsonpCallback"></script>  
    <!-- 传递固定参数的方式 -->
    <!-- <script type="text/javascript" src="http://180.167.10.100/update/index.php?os=Linux&version=3.3.3&callback=jsonpCallback"></script> -->  
</body>
</html>

或者

<!doctype html>
<html lang="en">
<head></head>
<body>
    <script type="text/javascript">
        function jsonpCallback(result) {  
            //alert(result);  
            for(var i in result) {  
                console.log(i+":"+result[i]);//循环输出result的元素  
            }  
        }  
        var JSONP=document.createElement("script");  
        JSONP.type="text/javascript";  
        JSONP.src="http://180.167.10.100/update/index.php?callback=jsonpCallback";  
        document.getElementsByTagName("head")[0].appendChild(JSONP); 
        /*传递参数的方式*/
        /*JSONP.src="http://180.167.10.100/update/index.php?os=Linux&version=3.3.3&callback=jsonpCallback";*/  
    </script>
</body>
</html>    

服务端(b域名)的php代码:

<?php    
    //服务端返回JSON数据  
    //$arr=array(‘a‘=>1,‘b‘=>2,‘c‘=>3); 
    $arr->IsLatestVersion = False;
    $arr->version = ‘3.4.3’;
    $arr->url = "http://172.30.28.18/update/releases/3.2.1.601/Windows/cdos-browser2_3.2.1.601.exe"; 
    $result=json_encode($arr);  
    //动态执行回调函数  
    $callback=$_GET[‘callback‘];  
    echo $callback."($result)";  
?>

 

2.在实际的项目中,我们的jsonp一般不会直接在html文件中实现,因为向服务器获取数据的时机需要根据我们项目的实际需求来决定,所以一般需要动态的在我们所开发的模块或某个js文件中创建script标签以及传递相关参数(此参数也可能是项目过程中动态生成的),这时我们可以在客户端(a域名)的js文件中如下实现:

// 得到查询结果后的回调函数
function jsonpCallback(result) {  
    for(var i in result) {  
        console.log(i+":"+result[i]);//循环输出result的元素  
    }  
}
function getVersionInfo(){
    var os = "Windows";
    var version = "3.3.3";
    var head = document.getElementsByTagName(‘head‘)[0];         
    var script = document.createElement(‘script‘);//创建script标签,设置其属性         
    var url = "http://180.167.10.100/update/index.php?os="+os+"&version="+version+"&callback=jsonpCallback";
    script.type = "text/javascript";                            
    script.setAttribute(‘src‘, url);//script.src= http://www.mamicode.com/url;//提供jsonp服务的url地址
    head.appendChild(script);// 把script标签加入head,此时调用开始
}
getVersionInfo();    //具体根据实际情况在合适位置调用即可

但是此时很可能出现报错“jsonpCallback未定义”,原因其实很简单,作为jsonp的回调函数,jsonpCallback必须是全局函数,而一般由于项目的模块化和封装我们的函数都是局部函数,此时我们必须将其全局化,可以将此回调函数jsonpCallback单拿出来放在html文件中,或者将其通过如下方法绑定到window对象上实现全局化:

(function(){        
    // 得到查询结果后的回调函数
    window[‘jsonpCallback‘] = function(data){
        for(var i in result) {  
            console.log(i+":"+result[i]);//循环输出result的元素  
        }
    };
    function getVersionInfo(){
        var os = "Windows";
        var version = "3.3.3";
        var head = document.getElementsByTagName(‘head‘)[0];         
        var script = document.createElement(‘script‘);//创建script标签,设置其属性         
        var url = "http://180.167.10.100/update/index.php?os="+os+"&version="+version+"&callback=jsonpCallback";
        script.type = "text/javascript";                            
        script.setAttribute(‘src‘, url);//script.src= http://www.mamicode.com/url;//提供jsonp服务的url地址
        head.appendChild(script);// 把script标签加入head,此时调用开始
    }
    getVersionInfo();//具体根据实际情况在合适位置调用即可
})();

这样jsonp的原理就很清楚了,首先在客户端注册一个callback(名字任意),然后动态创建script标签(类似引入js文件的方式)通过src引入服务器端的php文件,同时将客户端注册的callback的名字传给服务器,php文件载入成功后,服务器先生成我们需要的 json 数据,然后将其作为参数传入我们在url参数中指定的函数,所以jsonp是需要服务器端的页面进行相应的配合的。

二、jquery实现jsonp

jquery本身就支持JSONP。但是jsonp的方式只是针对get请求方式,不支持post请求。这也是Jsonp方式的局限性

实现原理是一样的,只不过我们不需要手动的插入script标签以及定义回调函数。jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

jquery在处理jsonp类型的ajax时(还是忍不住吐槽,虽然jquery也把jsonp归入了ajax,但其实它们真的不是一回事儿),自动帮你生成回调函数并把数据取出来供success属性方法来调用

 。

 

2.

 

 

  

 

二、JSON和

 

知道jsonp跨域的原理后我们就可以用js动态生成script标签来进行跨域操作了,而不用特意的手动的书写那些script标签。如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。

技术分享

 

跨域问题相关知识详解(原生js和jquery两种方法实现jsonp跨域)