项目中因为要把问卷投放到第三方,并且对方要收集统计数据,因此在调用对方接口的时候就会存在跨域的问题。
1. 什么叫js跨域
浏览器因为安全考虑,所以设置了同源策略。同源策略简单理解就是DNS域名,端口号,协议完全相同就称为同源,同源下的页面之间才能进行js的dom操作,如果不在同一个源下任何跨文档dom访问都是被阻止的。不同源下的访问可以称之为跨域。
下面表格里的a.js是无法获取b.js的内容的。
情况 | 举例 |
端口号不同 |
http://www.baidu.com/a.js vs. http://www.baidu.com:8080/b.js |
主域相同子域不同 |
http://www.baidu.com/a.js vs. http://blog.baidu.com/b.js |
协议不同 |
http://www.baidu.com/a.js vs. https://www.baidu.com/b.js |
域名不同 |
http://www.baidu.com/a.js vs. http://www.qq.com/b.js |
域名和对应ip |
http://www.baidu.com/a.js vs. http://192.168.2.2/b.js |
2. 禁止js跨域的原因
首先来谈一下为什么要禁止js跨域。通过各方总结,我个人的理解是js之所以要禁止跨域是防止某些恶意的开发者通过接口来向网站发起请求,获取数据库中的重要数据,例如用户名,密码等。
所以浏览器禁止js跨域请求数据。说白点就是浏览器不允许你去拿别人网站的东西。
那么有的时候我们需要进行这种跨域请求操作,例如问卷这个项目,怎么办。。
3. js跨域解决方法
之前看了网上很多帖子和方法,我想在这里说的是,只要是解决js跨域问题,都必须在服务器端做修改。因为只有对方允许你进行操作,你才能进行相应的读写操作,否则如果服务器不用做任何改动,那浏览器禁止跨域还有什么意义呢。
接下来说一下常见的几种解决方案:
1. 服务器端修改字段
服务器端设置 Access-Control-Allow-Origin:* 或者 Access-Control-Allow-Origin:http://www.baidu.com
header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Origin: http://www.baidu.com");
2. jsonp的格式
有两种方式:
1>利用script src标签
因为浏览器并没有禁止引用跨域的js代码,因此可以把数据放在函数的形参里,返回给页面。页面设置好方法名后就可以调用参数了。
客户端需要做的两件事:动态生成script标签;设置回调函数。
<script> var callbackFunc = function (data) { // name: xxx, age: 15 console.log('name:' + data.name + 'age:' + data.age); } var script = document.createElement('script'); script.src = 'http://www.a.com/a.js'; document.head.appendChild(script); </script>
服务器端定义回调函数,并把需要传递的参数放到函数中,作为形参传递。(其中回调函数的名称要和客户端的保持一致)
callbackFunc({ name: 'xxx', age: '15' });
但是如果不同的客户端定义的回调函数不同,那么服务器端怎么传递定义回传的函数并把数据传递回去呢。这个时候可以在传递url的时候加上个参数'http://www.a.com/a.js?callback=callbackFunc'。这个参数也就是告诉服务器我定义的回调函数是callbackFunc,你要是回传参数的时候,就用这个函数名吧,然后客户端去取到参数名,拼接函数名传递返回值。格式同上面那个相同。(其中callback这个参数可以选择其他名字,只要和服务器端商定好就可,比如也可以换成?iLikeThisName=callbackFunc,当然一般没人这么写。。其实就是约定俗称了,没有道理)
之前一直困惑我的为什么要叫jsonp,我现在的理解是json with padding,就是用json格式的数据来填充js函数(这里的填充就是把json格式的数据作为形参),然后返回给客户端,所以就叫jsonp了,仅仅是我个人理解。。不知道对不对。。望指正。
2>利用ajax的get方法,返回jsonp格式数据。
这个地方相当于另一种写法,原理一致,也是用jsonp的形式。
$.ajax({ type: "get", url: "http://www.a.com/a.js", dataType: "jsonp", jsonp: "callback",//回调函数名 jsonpCallback:"callbackfunc",//自定义的jsonp回调函数名称 success: function(json){ console.log('name:' + json.name + 'age:' + json.age); }, error: function(){ console.log('fail'); } });
3.iframe方式(主域相同子域不同)-- document.domain
举个例子:如果在父页面嵌套一个iframe标签,里面的域名不同源。跨域的情况下,如果我在父页面执行js才做要读取子页面里的内容就会报错。如下图所示。
当然这个例子在iframe跨域方式下不太恰当,但是可以更直观的理解。iframe跨域是必须在主域相同的情况下,例如父页面是www.a.com,子页面是edu.a.com(其中.com是顶级域名,a.com是主域名,前面的www和edu则为子域名)。
在edu.a.com和www.a.com的页面分别设置document.domain = a.com就可以相互通信了。
可以在父页面用命令:document.getElementById('id').contentWindow.document或者window.frames['name'].document来调用子页面的dom结构。
(请注意,上面举的例子,再怎么设置也没用,因为根本主域就不同啊。。- -!)
4. html5方式 -- window.postMessage
这种方式是一种比较新的方式,利用postMessage API实现跨域请求。主要的思想是在请求方(http://getdata.a.com)添加iframe标签,将iframe标签的src属性指定为需要跨域请求的url地址。然后对需要处理的消息进行注册,注册方法里对传递源进行判断,看看是不是需要的url地址,并对参数进行处理。
在父页面添加代码:重点是addeventlisenter和receiveMessage方法。
<div class="content"> <script> //对传进来需要处理的消息进行注册 window.addEventListener("message", receiveMessage, false); function receiveMessage(event) { // For Chrome, the origin property is in the event.originalEvent object. var origin = event.origin || event.originalEvent.origin; if (origin !== "http://setdata.a.com") return; //传递的消息 console.log(event); } </script> <iframe src="a" frameborder="0" name="myframe" id="corelframe" class="corelframe"> </iframe> </div>
在传递数据的页面(http://setdata.a.com)添加函数:第一个参数表示需要传递的数据,第二个参数是请求方的地址。
parent.postMessage("hello there!", "http://getdata.a.com");
在请求方(http://getdata.a.com)打印event对象可以看到如下信息:
5.window.name
这个方法的精髓在于设置一个代理页面,因为代理页面和请求方是同一个域名,所以可以访问之前设置过的window.name属性。而这里也有一个概念就是虽然请求方的iframe重定向了location,但是iframe本身并没有新建,所以在第一次load iframe的时候(即访问跨域方时)设置的name属性依然还在,也因此可以在第二次load时,即访问代理方时取到iframe.contentWindow.name属性(也因此获得了跨域数据)。
具体方法:
假设: 1)请求方:http://getdata.a.com 2)代理方:http://getdata.a.com/proxy.html 3)传递数据方:http://setdata.b.com
步骤:1)在请求方代码添加代码:
var state = 0, iframe = document.createElement('iframe'), loadfn = function() { if (state === 1) { // 读取数据 var data = iframe.contentWindow.name; alert(data); //弹出'aaa' } else if (state === 0) {
// 这里主要是为了防止每次location重定向后刷新页面,所以在这里设置一个判断,如果重定向了,就不再进行重定向操作,也就不会再刷新页面了 state = 1; //这是代理页面 iframe.contentWindow.location = "http://getdata.a.com/proxy.html"; } }; iframe.src = 'http://setdata.a.com'; if (iframe.attachEvent) { iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe);
如果不加代理直接读取iframe.contentWindow.name的话会产生类似下面的错误:(即跨域)
2)新建一个代理页面(里面不添加任何内容即可)
3)在数据传递方添加代码:(把要传递的内容放在window.name里传递出去即可)
<script> window.name = 'aaa'; </script>
4. 不需要返回值的js跨域请求方法
经高人指点,如果不需要返回值,可以利用带有src属性的任何标签,将url链接添加到src链接上即可,例如img,iframe,script标签等。
总结:
现在常用的方法还是jsonp的形式比较多。但回想起问卷这个项目,是在url链接里传递id值,然后它会返回一个页面,所以直接采用按钮里设置click事件,然后添加window.location.href的形式跳转页面了。。感觉并没有用到那么多跨域的知识哇。。