跨域是当前Web开发不可忽视的情况,经常会遇到跟其他站点进行通信情况,跨域主要包括以下情况:
1. 同域名,不同端口
2. 同主域名,不同子域名
3. 同域名,不同协议
4. 不同域名
解决方案:
对于第二种情况可以使用,可以使用设置document.domain = xx.com来解决:
例如first.domain.com,second.domain.com 或者domain.com 这种情况下属于主域名相同
第一步在所有页面中设定 document.domain = "domain.com";
第二步创建Iframe然后通过
<script type=”text/javascript” > document.domain = 'domain.com'; var ifr = document.createElement('iframe'); ifr.src = 'http://second.domain.com/b.html'; ifr.style.display = 'none'; document.body.appendChild(ifr); ifr.onload = function(){ var doc = ifr.contentDocument || ifr.contentWindow.document; // 在这里操纵b.html alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue); }; //first.domain.com通过下面代码获取second.domain.com窗口的高度 var h = window.navigator.userAgent.indexOf("MSIE") > 0 ? document.body.scrollHeight : document.documentElement.scrollHeight; parent.parent.document.getElementById('name').height = h; //两个不同的子域名伪装成了同域,完全在同源策略的保护下进行通信
</script>
其他情况可以使用以下方式:
1. 服务端代理:
客户端在请求本域名的服务端脚本(PHP,.NET,Jsp或其他CGI), 服务端通过http请求目标域名,由于服务端是可以跨域的,所以服务端作为代理请求目标域名数据返回给本地域名,
好处是简单有效,坏处是经过服务端介入,性能稍微有点损耗。
2. iframe+hash
不同域名下可以通过iframe嵌入,然后通过hash传递数据,缺点是hash长度有限,并且不够安全,只能传递字符串
例如www.domain.com#abc 其中abc就是location.hash
例如a.com想访问b.com
第一步a.com创建指向b.com#abc的iframe
第二步a.com通过定时器检测iframe中的hash是否变化,如果变化重新获取hash值,IE,chrome浏览器不支持跨域修改parent.location.hash值,所以需要代理,但是firefox支持
a.com代码
function startRequest(){ var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://www.b.com/b.html#paramdo'; //根据paramdo来决定获取啥值 document.body.appendChild(ifr); } function checkHash() { try { var data = location.hash ? location.hash.substring(1) : ''; if (console.log) { console.log('Now the data is '+data); } } catch(e) {}; } setInterval(checkHash, 2000);
b.com页面的代码如下
//模拟一个简单的参数处理操作 switch(location.hash){ case '#paramdo': callBack(); break; case '#paramset': //do something…… break; } //设置a.com iframe src的location.hash function callBack(){ try { //此时是火狐正常执行 parent.location.hash = 'somedata'; } catch (e) { // ie、chrome的安全机制无法修改parent.location.hash, // 所以要利用一个中间的cnblogs域下的代理iframe var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://a.com/change.html#somedata'; // 注意该文件在"a.com"域下 document.body.appendChild(ifrproxy); } } //a.com域名下 change.html中的代码 //因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1);
3. Jsonp或动态创建script
Jsonp的原理就是利用<script>标签可以引入其他域名资源的方法,在需要跨域的脚本返回Json格式字符串,同时带上方法名,将Json作为参数返回,在本地服务器定义了该方法用来解析Json字符串,从而达到将跨域数据下载到本地的方式,例如:
服务器 www.local.com 文件local.html想访问 www.remote.com上的文件remote.php
local.html代码:
<html> <script src="http://www.remote.com/remote.php?call_back=ShowJson"></script> <script> funtcion ShowJson(json) { //解析json } </script> </html>
remote.php
<?php $call_back = $_GET['call_back']; $data = array("name"=>"michael","sex"=>"male"); echo $call_back.'('.json_encode($data).')'; ?>
这样本地文件可以跨域访问其他域名的数据,条件是其他域名文件需要进行call_back处理
4. window.name
window.name属性是同一个窗口生命周期内的全局变量,就是跳转的各个页面都可以访问同一个window.name对象,在跨域的场景中,可以通过代理iframe的方式同样获得window.name值例子:
a.com下的a.html
<script type="text/javascript"> function domainData(url, fn) { var isFirst = true; var iframe = document.createElement('iframe'); iframe.style.display = 'none'; var loadfn = function(){ if(isFirst){ iframe.contentWindow.location = 'http://a.com/null.html'; isFirst = false; } else { fn(iframe.contentWindow.name); iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); iframe.src = ''; iframe = null; } }; iframe.src = url; if(iframe.attachEvent){ iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe); }
domainData('http://b.com/data.html', function(data){ alert(data); });
</script>
b.com下的data.html
<script> window.name = '需要跨域传递的数据'; </script>
null.html 是必须的。内容可为空。
调用domainData函数必须在body后面,或页面加载完后。
调用时会执行 http://b.com/data.html 页面的脚本。
5. HTML5 postMessage
Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+中可以使用HTML5新特性postMessage 来进行跨域处理:
a.html
<iframe id="ifr" src="b.com/index.html"></iframe> <script type="text/javascript"> window.onload = function() { var ifr = document.getElementById('ifr'); var targetOrigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样 // 若写成'http://c.com'就不会执行postMessage了 ifr.contentWindow.postMessage('I was there!', targetOrigin); }; </script>
b.html
<script type="text/javascript"> window.addEventListener('message', function(event){ // 通过origin属性判断消息来源地址 if (event.origin == 'http://a.com') { alert(event.data); // 弹出"I was there!" alert(event.source); // 对a.com、index.html中window对象的引用 // 但由于同源策略,这里event.source不可以访问window对象 } }, false); </script>
6. 使用JS框架,如jQuery和ExtJs
jQuery和ExtJs已经封装了跨域Ajax请求,基本原理也是利用Jsonp方式,使用方法如下:
1) jQuery:
简化方式
//跨域(可跨所有域名) $.getJSON("http://user.hnce.com.cn/getregion.aspx?id=0&jsoncallback=?",function(json){ //要求远程请求页面的数据格式为: ?(json_data) //例如: //?([{"_name":"湖南省","_regionId":134},{"_name":"北京市","_regionId":143}]) alert(json[0]._name); });
定制化方式:
$.ajax({ type : "get", async:false,//是否异步 url : "http://www.xxx.com/ajax.do", dataType : "jsonp", jsonp: "callbackparam",//服务端用于接收callback调用的function名的参数 jsonpCallback:"success_jsonpCallback",//callback的function名称 success : function(json){ alert(json); alert(json[0].name); }, error:function(){ alert('fail'); } });
//服务端代码: public void ProcessRequest (HttpContext context) { context.Response.ContentType = "text/plain"; String callbackFunName = context.Request["callbackparam"]; context.Response.Write(callbackFunName + "([ { name:\"John\"}])"); }
2)Extjs跨域方式:
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); } });
使用ExtJs4.0以前的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' } });