跨域终极解决办法
这两天用Ajax连接后端JSON-RPC的服务, 后端的service是用C++写的,用到的是cppcms,作者给的例子是python写的,需要设置Content-Type:application/json,经测试,python可以正常访问到这个service。
后端由于已经限定了Content-Type, 考虑到需要和其他服务器端通信也需要到这个设置,不好做出修改。所以在浏览器端也要进行该设置,在查阅了资料后,发现请求头的类型可以自己任意设置,例如 setRequestHeader(name,value);
新建了一个本地的html测试后,设置了xhr.setRequestHeader(“Content-Type”,”application/json”);
测试的结果是ie8 能够正常设置Content-Type,能够和后端交互,服务器端返回status200, 获得正确结果。
但是chrome里和ff里反复设置content-type,都不成功,在浏览器的网络面板里也没看到发出去的请求。用fiddler等工具也没有发现正确的请求头部和正确的应答结果。
在chrome里只有设置了xhr.setRequestHeader(“Content-Type”,”application/x-www-form-urlencoded,application/json; charset=utf-8″);才能在网络面板里看到,但是被浏览器给canceled掉了.
而且Content-Type中在第二个设置了application/json也不能通过,从这个可以发现,application/json; 必须要设置为第一个参数。
调了很长都未果,后来用firebug的rest插件模拟一个请求,设置content-type:application/json, 参数“{‘method’:’sum’,’params’:[1,2],’id’:1}”,得到正确的结果3,考虑到这些发现了是一个跨域的情况,解决了同源策略问题后,得到了正确的结果,Content-Type也正确的设置上了。
开始时由于没有正确的设置上Content-Type,思路限定在Content-Type值的设置方法上了,例如HTTP请求一些设置依赖关系,例如是否要同时设置上Accept,Content-length等。 最后才发现是同源策略影响到了setRequestHeader正确的设置。
简单说一下JSON-RPC
JSON-RPC分为1.0 和2.0
1.0的参数设置 可以参考http://json-rpc.org/wiki/specification
method AString containing the name of the method to be invoked. params AnArray of objects to pass as arguments to the method. id The request id. This can be of anytype. It is used to match the response with the request that it is replying to.method:是请求的方法名
params:是一个参数数组
id:是一个唯一的id
一个例子是“{‘method’:’sum’,’params’:[1,2],’id’:1}”;
2.0多加了一个版本号 参考 http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
{“jsonrpc”: “2.0″, “method”:”subtract”, “params”: [42, 23], “id”: 1}
在项目中用到的是1.0的格式
测试代码如下
帮助Source Code123456789101112var xhr = new XMLHttpRequest();var params = "{'method':'sum','params':[1,2],'id':1}";xhr.open("post", “path /rpc”, true);xhr.setRequestHeader("Content-Type","application/json"); //要再open方法后调用xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 &&xhr.status < 300 || xhr.status === 304) { alert("ok") } }}xhr.send(params)如果用jquery调用更简单
Source Code1234567891011121314151617181920
$.ajax({ url: “/rpc", type: "post", dataType: "json", data: '{"method":"sum","params":[1,2],"id":1}', contentType: "application/json; charset=utf-8", beforeSend: function(x) { //这个不需要设置,要不可能在有得浏览器中出现重复的content-type //x.setRequestHeader("Content-Type", "application/json;charset=utf-8"); }, success: function(json) { alert("ok"); }, error: function(x, e) { alert("error"); }, complete: function(x) { alert("complete"); }})Web开发由于需要经常进行ajax请求,很多时候涉及到跨域,由于浏览器的同源策略,所以会影响到正确的请求和返回数据。
关于同源策略(Same Origin Policy)
最早源于NetscapeNavigator 2.0,在浏览器端是一个重要的安全策略。同源是指,域名相同,应用层协议相同,端口相同,当这些属性完全一致时时才被认为是同源。所以当遇到下面的访问时都要考虑到跨域的问题。
说明:
http://www.test.com/
父域名是test.com.
子域是
www.test.com
lab.test.com等
举一个例子
假如当前页面是
http://www.test.com/1/index.html
比较的url结果原因http://www.test.com/1/index.htmlhttp://www.test.com/1/index.html
成功同协议同hosthttp://www.test.com/1/index.htmlhttp://www.test.com/2/index.html
成功同协议同host不同文件夹http://www.test.com/1/index.html
http://www.test.com:82/1/other.html
失败不同端口80和82http://www.test.com/1/index.html
https://www.test.com/1/other.html
失败不同协议http — httpshttp://www.test.com/1/index.html
http://en.test.com/1/other.html
失败不同host(子域不同)www —enhttp://www.test.com/1/index.html
http://test.com/1/other.html
失败不同hostwww—testhttp://www.test.com/1/index.html
http://other.www.test.com/dir/other.html
失败不同hostwww—other.www
跨域的一些方案
1 跨子域之间的相互访问
例如父域名都是test.com,子域是www.test.com 和other.test.com,
例如在www.test.com要通过ajax请求other.test.com页面里的一个txt页面other.test.com/1.txt.这样由于是跨域是不能得到的.
不过这个问题是跨子域,可以通过把两个页面都要设置domain = test.com”。
在主页面
test.com的html里
首先要包括子页面的一个html文件
然后设置domain
document.domain = “ test.com”;
获得这个iframe的窗口,这里用jquery框架方便调用
contentWindow是获得框架中的窗口,类似于window
1var crossIframe = document.getElementById(‘ crossIframe ‘).contentWindow.$;2?crossIframe.get(“http://other.test.com/1.txt”,function(data){3?????alert(“成功“);4?});5?}在other.test.com/iframe.html里要include jquery库文件
同样也要也要设置
document.domain = “test.com”;
这样就可以在test.com的html页面中成功获取1.txt了
2 用注入方式跨域,经常说的jsonp
注入的主要跨域思路是建一个script脚本,append进head或者是body.
有两种方法
第一个种是在
该脚本会向www.xxxxx.com/getResult发送请求,该服务返回的格式要是javascript可以执行的代码,当script调用成功后,会自动调用callback.
这里需要的就是www.abc.com 里要有一个callback函数,以便刚才的script脚本成功执行后的回调。
function callback(data){
alert(data); //data为script脚本成功调用返回的数据
}
动态注入的方法:
把刚才的url传入即可,动态生成一个script元素,然后插入document或者head
1function JSON(url){ // 调用JSONP服务器,url为请求服务器地址2?????var script =document.createElement(“script”);3?????script.setAttribute(“type”,”text/javascript”);4?????script.setAttribute(“src”,url);5?????script.setAttribute(“id”,url);6?????document.appendChild(script);7?}如果利用jquery会更方便些
例如
1Var url = “<a href="http://www.xyz.com/getResult">www.xyz.com/getResult</a>?callback=?”;2?$.get(url, {“name”:”hiro”}, function(data){ },”json”);1.2版本以上只要上面写一个占位符?就可以,Jquery会把自动会把callback=?转换为注入方式
或者用jquery的$.ajax
?
1$.ajax({2????type:“get”,3????data:{},4????url:url,5????dataType:“jsonp”,6????jsonp:“callback”,7????success:function(data){}8});<strong></strong>?
3利用iframe进行两个站点的跨域
刚才所说的是跨子域的iframe方法,如果跨两个域的方法会更麻烦一些,利用iframe + location hash值进行通信。
4用后端语言作为中间层
在本域下,由于服务器端语言没有跨域的限制,所以可以用例如java c#,python等去请求服务,然后再返回本域的数据。