1.什么是跨域?
跨域,JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。简单地理解就是因为JavaScript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象。
同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当一个百度浏览器执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。
更详细的说明可以看下表:
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 |
不同域名 | 不允许 |
特别注意两点:
(1).如果是协议和端口造成的跨域问题“前台”是无能为力的,
(2).在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。
2.跨域请求数据解决方案
(1).document.domain+iframe的设置
对于主域相同而子域不同的例子,可以通过设置document.domain的办法来解决。
(2).动态创建Script
虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以*执行引入的JS文件中的function(包括操作cookie、Dom等等)。
(3).利用iframe和location.hash
这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用location.hash来进行传值。
(4).Window.name实现的跨域数据传输
iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
(5).使用HTML5 postMessage
HTML5中最酷的新功能之一就是 跨文档消息传输Cross Document Messaging。
(6).利用flash
上述,六种方式都可以处理JavaScript的跨域请求数据问题,详细参见:Rain Man的《JavaScript跨域总结与解决办法》。
除了上面六种方式,大家平时估计都在用脚本框架开发,在JQuery框架和ExtJs框架中处理JS跨域问题,常用JSONP来处理。
3.什么是JSONP?
JSONP(JSON with Padding)是一个非官方的协议,它允许在服务器端集成Script Tags返回至客户端,通过Javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。
由于同源策略的限制,XMLHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。
4..JSONP如何产生的?
(1).跨域访问无权限。
众所周知的问题,Ajax直接请求普通文件存在跨域无权限访问的问题,不管是静态页面、动态网页、web服务、WCF,只要是跨域请求,都无权限;
(2)."src"属性标签有跨域能力。
发现在Web页面上调用js文件时则不受是否跨域的影响(不仅如此,拥有"src"属性的标签都拥有跨域能力,比如<script>、<img>、<iframe>);
(3).将数据装进JS格式数据。
如果想通过纯Web端(ActiveX控件、服务端代理、HTML5之Websocket等方式不算)跨域访问数据就只有一种可能,就是在远程服务器上设法把数据装进js格式的数据里,供客户端调用和进一步处理;
(4).JSON格式承载数据适合。
有一种JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被JS原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据;
(5).动态生成JSON格式数据。
Web客户端可以通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件,显而易见,服务器之所以要动态生成JSON文件,目的在于把客户端需要的数据装入进去。
(6).JSON数据成功回调到客户端。
客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但实质上是不一样。
(7).形成一种非正式传输协议JSONP。
为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回的数据。
5.JSONP的工作原理
JSONP的原理:创建一个回调函数,动态创建Script标签,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。将JSON数据填充进回调函数,进行相关的逻辑处理,或许这就是JSONP的JSON+Padding的含义。
(1).跨域简单原理
新建一个asp.net的web程序,添加sample.html网页和一个test.js文件,代码如下:
sample.html的代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>test</title>
<script type="text/javascript" src="test.js"></script>
</head>
<body>
</body>
</html>
test.js的代码:
alert("success");
打开sample.html后会跳出"success”这样的这样的信息框,这似乎并不能说明什么, 跨域问题到底怎么解决呢?
现在模拟非同源的环境,把上面的Web程序叫做A程序,再新建一个Web程序叫做B程序,将A程序的test.js文件移除然后拷贝到B程序中。将两个程序都运行起来,Visual Studio会启动内置服务器,假设A程序是localhost:20001,B程序是localhost:20002,这就模拟了一个非同源的环境了(虽然域名相同但端口号不同,所以是非同源的)。
现在改下A程序sample.html里的代码,因为test.js文件在B程序上了,url也就变成了localhost:20002。
sample.html部分代码:
<script type="text/javascript" src="http://localhost:20002/test.js"></script>
请保持AB两个Web程序的运行状态,当你再次刷新A程序localhost:20001/sample.html的时候,和原来一样跳出了"success"的对话框,这样就成功访问到了非同源的B程序localhost:20002/test.js这个所谓的远程服务了。到这里,大家应该已经大概明白如何跨域访问的原理了。
<script>标签的src属性并不被同源策略所约束,所以可以获取任何服务器上脚本并执行。
(2).跨域实现CallBack
继续修改代码,实现JSONP的JavaScript callback形式。
修改程序A中sample的代码:
<script type="text/javascript">
//回调函数
function callback(data) {
alert(data.message);
}
</script>
<script type="text/javascript" src="http://localhost:20002/test.js"></script>
程序B中test.js的代码:
//调用callback函数,并以json数据形式作为阐述传递,完成回调
callback({message:"success"});
这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。
(3).跨域实现动态JS脚本
怎么让远程js知道它应该调用的本地函数叫什么名字?毕竟是jsonp的服务者都要面对很多服务对象,而这些服务对象各自的本地函数都不相同。只要服务端提供的js脚本是动态生成的就可以,这样调用者可以传一个参数过去告诉服务端“我想要一段调用XXX函数的js代码,请你返回给我”,于是服务器就可以按照客户端的需求来动态生成js脚本并响应了。
程序A中sample的代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
// 得到航班信息查询结果后的回调函数
var flightHandler = function(data){
alert('你查询的航班结果是:票价 ' + data.price + ' 元,余票 ' + data.tickets + ' 张。');
};
// 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
// 创建script标签,设置其属性
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.setAttribute('src', url);
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
</script>
</head>
<body>
</body>
</html>
这样不直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,其重点也就在于如何完成jsonp调用的全过程。
看到调用的url中传递了一个code参数,告诉服务器我要查的是CA1998次航班的信息,而callback参数则告诉服务器,本地回调函数叫做flightHandler,所以请把查询结果传入这个函数*本地调用。
程序B中test.js的代码:
flightHandler({
"code": "CA1998",
"price": 1780,
"tickets": 5
});
我们看到,传递给flightHandler函数的是一个json,它描述了航班的基本信息。运行一下页面,成功弹出提示窗口,jsonp的执行全过程顺利完成!
6.JQuery和ExtJs实现JSONP
(1).JQuery的JSONP跨域实现
<1>.$.getJSON
jQuery框架支持JSONP,可以使用$.getJSON(url,[data],[callback])方法(详细可以参考http://api.jquery.com/jQuery.getJSON/)。继续修改程序A的代码,改用jQuery的getJSON方法来实现(下面的例子没用用到向服务传参,所以只写了getJSON(url,[callback])):
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$.getJSON("http://localhost:20002/MyService.ashx?callback=?",function(data){
alert(data.name + " is a a" + data.sex);
});
</script>
要注意的是在url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个问号是内部自动生成的一个回调函数名。这个函数名可以debug看一下,比如jQuery17207481773362960666_1332575486681。
<2>.$.ajax
假如说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?可以使用$.ajax方法来实现(参数较多,详细可以参见http://api.jquery.com/jQuery.ajax)。
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$.ajax({
url:"http://localhost:20002/MyService.ashx?callback=?",
dataType:"jsonp",
jsonpCallback:"person",
success:function(data){
alert(data.name + " is a a" + data.sex);
}
});
</script>
jsonpCallback就是指定我们自己的回调方法名person,远程服务接受callback参数的值就不再是自动生成的回调名,而是person。dataType是指定按照JSOPN方式访问远程服务。
(2).ExtJs的JSONP跨域实现
<1>.Ext.data.JsonP.request
ExtJS4.1的Ext.data.JsonP.request实现跨域访问:
//跨域请求,MsgUrl为其他站点地址
Ext.data.JsonP.request({
url: MsgUrl + '/Home/InitializeComet',
timeout: 300000,
params: { loginId: LoginId },
callbackKey: "jsonPCallback",
success: function(result) {
if (result.rettype == 'true') {
me.Comet.privateToken = result.msg;
me.RegisterComet();
} else {
alert(result.msg);
}
},
failure: function(result) {
alert(result);
}
});
其中跨域请求的要点是类名:Ext.data.JsonP和callbackKey的参数。
“jsonPCallback”该名称将作为Jsonp请求的方法名传递到服务器端,获取该请求的URL:
http://10.0.13.64:89/Home/InitializeComet?loginId=0001&jsonPCallback=Ext.data.JsonP.callback1&_dc=1370687739484
<2>.Ext.data.ScriptTagProxy
var ss = new Ext.data.ScriptTagProxy({
//url: 'http://10.128.3.104/edi/rest/GetBillCaseInfo',
url: 'testjson.do',
callbackParam: "_callback",
headers: { 'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=' }
});
ss.load({ '_out': 'json' },
new Ext.data.JsonReader(
{ root: "ROWSET.ROW" },
[{ name: 'CaseCode', mapping: 'CaseCode' },{ name: 'CaseName', mapping: 'CaseName'}]),
function (recordsBlock, arg, isok) {
alert(Ext.encode(recordsBlock));
alert(Ext.encode(recordsBlock.records[0].data));
}
);
Ext.Ajax.request({
url: 'http://10.128.3.104/edi/rest/GetBillCaseInfo',
//url: 'testjson.do',
scriptTag: true,
success: function (req) {
alert(req.responseText);
},
failure: function (req) {
alert(req.responseText);
},
headers: { 'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=' },
params: { _out: 'json' }
});
7.AJAX与JSONP的异同
(1).Ajax和Jsonp是两种技术。
Ajax和Jsonp这两种技术在调用方式上“看起来”很像,目的也一样,都是请求一个URL,然后把服务器返回的数据进行处理,因此JQuery和EXT等框架都把Jsonp作为Ajax的一种形式进行了封装;
(2).Ajax和Jsonp实现原理不同。
Ajax和Jsonp在本质实现上有差别。Ajax的核心是通过XmlHttpRequest获取非本页内容,而Jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。所以说,其实Ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,Jsonp本身也不排斥同域的数据的获取;
(3).Ajax和Jsonp都是非强制性协议。
Jsonp是一种方式或者说非强制性协议,如同Ajax一样,它也不一定非要用Json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用Jsonp提供公开服务。
总而言之,Jsonp不是Ajax的一个特例,哪怕Jquery、Ext等巨头把Jsonp封装进了Ajax,也不能改变这一点!
参考博客:
《Ajax与JSON的一些总结》
《JavaScript跨域总结与解决办法》
《深入浅出JSONP--解决ajax跨域问题》
《说说JSON和JSONP,也许你会豁然开朗,含jQuery用例》
《Jquery跨域请求》
《ExtJs学习笔记(23)-ScriptTagProxy+XTemplate+WCF跨域取数据》
《ExtJs与WCF之间的跨域访问》
《jQuery与Extjs的Ajax的跨域访问》