跨域解决方案JSONP

(转自jlin991的博客)

什么是跨域

老生常谈的问题了。下面列出一个表格:

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:8080/a.js http://www.a.com/b.js同一个域名,不同端口
http://www.a.com/a.js http://79.23.1.21/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一级域名和二级域名
http://www.XXX.com/a.js http://www.a.com/b.js不同域名
http://localhost/a.js http://127.0.0.1/b.js本地和本地对应的IP

牢记:只要协议schema ,域名,子域名,端口有一个不同的都是跨域!

而且跨域问题上,域仅仅是通过URL的首部来识别的,而不会去尝试判断两个域是否在同一个IP上(这涉及到DNS解析)

下面介绍几种能实现跨域的方法

图像Ping

JS高程提到的一个方法,利用的是img的src可以跨域。

var img= new Image();
img.onload=img.onerror=function(){
    console.log('Done~!');

};

img.src='http://www.example.com/test?name=Nicolas';
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这个方法唯一的能力就是可以知道是否接受到了响应,但是浏览器无法得到任何信息,但是可以请求服务器,比如可以请求服务器做一个删除的操作,或者创建的操作,因为querystring可以通过img.src提交给服务器。

仅仅是 浏览器 –> 服务器

1.只能发送get请求,并且无法获得任何服务器的响应。
2. 仅仅是单向的数据传递,浏览器-->服务器
  • 1
  • 2

JSONP

JSON with Padding 
JSONP 两部分组成: 回调函数+数据 
JSONP中的回调函数 
响应到来的时候,应该在页面中调用的函数。 
JSON就是数据 
数据是传入回调函数中的JSON数据。

JSONP的原理

动态添加一个<script>标签,而script标签的src属性是没有跨域的限制的。 和img的src一样可以不受限制从其他域加载资源。我们不能跨域请求数据,但是可以引入不同域的脚本文件。 所以我们JSONP中,src是我们请求服务器的url,一般是这样的形式:

http://crossdomain.com/jsonServerResponse?jsonp=jsonpCallback
  • 1

用法

动态script元素来使用,用js动态生成script标签来进行跨域操作了。

<script type="text/javascript">
    function jsonpCallback(result) {
        alert(result.a);
        alert(result.b);
        alert(result.c);
        for(var i in result) {
            alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
        }
    }
</script>
<script type="text/javascript" src="http://crossdomain.com/services.php?callback=jsonpCallback"></script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们看到,我们在客户端做的事情,就是定义一个回调函数,在请求完毕后执行它,然后在这个函数里面处理接收到的响应数据。 
看到URL请求参数,也可以发现,我们需要将函数名传递给callback这个请求参数。

分析

其实这里叫callback还是jsonp是由服务端决定的,服务端会根据接收到的这个请求,生成所需的json数据,当然你还可以附加一大堆请求参数到url上。然后服务端根据这些参数生成json数据传入到回调函数中。

返回的格式如: 
jsonpCallback({msg:”xxx”})

执行流程

客户端注册一个callback,这里是jsonpCallback,然后把callback的名字传给服务器,服务端得到这个函数名之后,要用这样的方法

jsonpCallback(...)
  • 1

包裹要输出的json内容。 此时服务器生成的json数据才能被客户端正确接收。 
然后以JS语法的方式生成一个function,function的名字就是传递上来的参数callback的值,这里的值”jsonCallback” 
最后将json数据直接入参的方式,放置到function中,这样就生成了一段js语法的文档,并返回给客户端。 
返回一个script文档

<script>
...
</script>
  • 1
  • 2
  • 3

客户端解析script标签,并执行返回的js文档,此时js文档数据,作为参数传入到了客户端预先定义好的callback函数里。

可以说jsonp的方式原理上和<script src=”http://跨域/…xx.js”&ht;</script>是一致的(qq空间就是大量采用这种方式来实现跨域数据交换的)。JSONP是一种脚本注入(Script Injection)行为,所以有一定的安全隐患。

服务端代码可能是这样的

<?php

//服务端返回JSON数据
$arr=array('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5);
$result=json_encode($arr);
//echo $_GET['callback'].'("Hello,World!")';
//echo $_GET['callback']."($result)";
//动态执行回调函数
$callback=$_GET['callback'];//得到回调函数
echo $callback."($result)";//输出,result作为参数传递
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

服务端做的事 
我们其实是要querystring,获取url中传递的参数,这里是callback,然后把callback的值’jsonpCallback’包裹要传递的json字符串。也就像这样: 
jsonpCallback({“name”:”xxx”,”id”:23});

返回的就是用这个callback的值包裹的json数据。 
然后这个回调函数就会被执行。因为你创建了script标签然后函数在里面被调用执行了。

jQuery的实现

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

jQuery源码部分:
//Build temporary JSONP function
if( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
    jsonp = s.jsonpCallback || ("jsonp" + jsc++);

    // Replace the =? sequence both in the query string and the data
    if ( s.data ) {
    s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
    }
 /*...*/
 if ( s.cache === false && type === "GET" ) {
var ts = now();

// try replacing _= if it is there
var ret = s.url.replace(rts, "$1_=" + ts + "$2");

// if nothing was replaced, add timestamp to the end
s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
}

// If data is available, append data to url for get requests
if ( s.data && type === "GET" ) {
s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
}

// Watch for a new set of requests
if ( s.global && ! jQuery.active++ ) {
jQuery.event.trigger( "ajaxStart" );
}

// Matches an absolute URL, and saves the domain
var parts = rurl.exec( s.url ),
remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);

// If we're requesting a remote document
// and trying to load JSON or Script with a GET
if ( s.dataType === "script" && type === "GET" && remote ) {
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
script.src = s.url;

// Handle Script loading
if ( !jsonp ) {
var done = false;

// Attach handlers for all browsers
script.onload = script.onreadystatechange = function() {
 if ( !done && (!this.readyState ||
 this.readyState === "loaded" || this.readyState === "complete") ) {
 done = true;
 success();
 complete();

 // Handle memory leak in IE
 script.onload = script.onreadystatechange = null;
 if ( head && script.parentNode ) {
 head.removeChild( script );
 }
 }
};
}

// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );

// We handle everything using the script element injection
return undefined; 

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

jQuery一开始先判断JSON类型的调用,然后为本次调用创建了临时的JSONP方法,并且添加了一个随机的数字(源于日期值) 
然后创建了script标签,构造这个script片段,并且追加到了原文档的head标签后面。 
还有就是判断浏览器的脚本onreadystatechange事件,判断readyState是loaded或complete两个都要判断。 
NOTE 
因为无论是哪个属性都表示资源已经可用了。有时候readyState会停在“loaded”有时候会跳过”loaded”直接完成)

JSONP的优点

  1. JSON可读性好,在JS中容易处理
  2. 比XML轻了很多
  3. PHP对JSON的支持也不错

JSONP的弊端

JSONP很好用,但是它有如下的缺点:

  1. JSONP是从其他域中加载代码执行的,如果其他域不安全(服务器端不安全),很有可能在响应中夹带一些恶意代码,此时除了完全放弃JSONP调用外没有办法。
  2. 其次,确定JSONP是否请求成功不容易。因为如果动态脚本插入有效那么就执行调用,如果无效就静默失败,失败没有任何提示~
  3. 用eval()解析也是容易出现安全问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值