(转自jlin991的博客)
什么是跨域
老生常谈的问题了。下面列出一个表格:
牢记:只要协议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的优点
- JSON可读性好,在JS中容易处理
- 比XML轻了很多
- PHP对JSON的支持也不错
JSONP的弊端
JSONP很好用,但是它有如下的缺点:
- JSONP是从其他域中加载代码执行的,如果其他域不安全(服务器端不安全),很有可能在响应中夹带一些恶意代码,此时除了完全放弃JSONP调用外没有办法。
- 其次,确定JSONP是否请求成功不容易。因为如果动态脚本插入有效那么就执行调用,如果无效就静默失败,失败没有任何提示~
- 用eval()解析也是容易出现安全问题