因为安全的缘由,浏览器作了不少方面的工做,由此也就引入了一系列的跨域问题,须要注意的是:javascript
跨域并不是浏览器限制了发起跨站请求,而是跨站请求能够正常发起,可是返回结果被浏览器拦截了。最好的例子是 crsf
跨站攻击原理,请求是发送到了后端服务器不管是否跨域!注意:有些浏览器不容许从HTTPS的域跨域访问HTTP,好比Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例html
JSONP
的全称是 "JSON With Padding", 词面意思上理解就是 "填充式的JSON"。它不是一个新鲜的东西,隶属于 JSON
的一种使用方法,或者说是一种使用模式,能够解决一些常见的浏览器端网页跨域问题。java
正如他的名称同样,它是指被包含在调用函数中的JSON,好比这样:git
callback({"Name": "小明", "Id" : 1823, "Rank": 7})
因为 jQuery
的一些缘由,使得 JSONP
经常与 Ajax
混淆。实际上,他们没有任何关系。github
因为浏览器的同源策略,使得在网页端出现了这个“跨域”的问题,然而咱们发现,全部的 src
属性并无受到相关的限制,好比 img
/ script
等。ajax
JSONP
的原理就要从 script
提及。script
能够执行其余域的js
函数,好比这样:chrome
a.html ... <script> function callback(data) { console.log(data.url) } </script> <script src='b.js'></script> ... b.js callback({url: 'http://www.rccoder.net'})
显然,上面的代码是能够执行的,而且能够在console里面输出http://www.rccoder.netjson
利用这一点,假如b.js里面的内容不是固定的,而是根据一些东西自动生成的, 嗯,这就是JSONP的主要原理了。回调函数+数据就是 JSON With Padding
了,回调函数用来响应应该在页面中调用的函数,数据则用来传入要执行的回调函数。后端
至于这个数据是怎么产生的,说粗鲁点无非就是字符串拼接了。跨域
简单总结一下: Ajax 是利用 XMLHTTPRequest 来请求数据的,而它是不能请求不一样域上的数据的。可是,在页面上引用不一样域的 js 文件倒是没有任何问题的,这样,利用异步的加载,请求一个 js 文件,而这个文件的内容是动态生成的(后台语言字符串拼接出来的),里面包含的是 JSON With Padding(回调函数+数据),以前写的那个函数就由于新加载进来的这段动态生成的 js 而执行,也就是获取到了他要获取的数据。
重复一下,在一个页面中,a.html这样写,获得 UserId 为 1823 的信息:
a.html ... src="http://server2.example.com/RetrieveUser?UserId=1823&callback=parseResponse"> ...
请求这个地址会获得一个能够执行的 JavaScript。好比会获得:
parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7})
这样,a.html里面的 parseResponse()
这个函数就能执行而且获得数据了。
等等,jQuery到底作了什么:
jQuery 让 JSONP 的使用API和Ajax的如出一辙:
$.ajax({ method: 'jsonp', url: 'http://server2.example.com/RetrieveUser?UserId=1823', success: function(data) { console.log(data) } })
之因此能够这样是由于 jQuery 在背后倾注了心血,它会在执行的时候生成函数替换callback=dosomthing
,而后获取到数据以后销毁掉这个函数,起到一个临时的代理器做用,这样就拿到了数据。
JSONP 的后话:
JSONP的这种实现方式不受同源策略的影响,兼容性也很好;可是它之支持 GET 方式的清楚,只支持 HTTP 请求这种特殊的状况,对于两个不一样域之间两个页面的互相调用也是无能为力。
XMLHttpRequest
的同源策略看起来是如此的变态,即便是同一个公司的产品,也不可能彻底在同一个域上面。还好,网络设计者在设计的时候考略到了这一点,能够在服务器端进行一些定义,容许部分网络访问。
CORS 的全称是 Cross-Origin Resource Sharing,即跨域资源共享。他的原理就是使用自定义的 HTTP 头部,让服务器与浏览器进行沟通,主要是经过设置响应头的 Access-Control-Allow-Origin
来达到目的的。这样,XMLHttpRequest 就能跨域了。
值得注意的是,正常状况下的 XMLHttpRequest 是只发送一次请求的,可是跨域问题下极可能是会发送两次的请求(预发送)。
更加详细的内容能够参见:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
CORS 的后话:
相比之下,CORS 就支持全部类型的 HTTP 请求了,可是在兼容上面,每每一些老的浏览器并不支持 CORS。
Desktop:
浏览器 | 版本 |
---|---|
Chrome | 4 |
Firefox (Gecko) | 3.5 |
Internet Explorer | 8 (via XDomainReques) 10 |
Opera | 12 |
Safari | 4 |
Mobile:
设备 | 版本 |
---|---|
Android | 2.1 |
Chrome for Android | yes |
Firefox Mobile (Gecko) | yes |
IE Mobile | ? |
Opera Mobile | 12 |
Safari Mobile | 3.2 |
window.name 在一个窗口(标签)的生命周期以内是共享的,利用这点就能够传输一些数据。
除此以外,结合 iframe 还能实现更增强大的功能:
须要3个文件: a/proxy/b
a.html <script type="text/javascript"> var state = 0, iframe = document.createElement('iframe'), loadfn = function() { if (state === 1) { var data = iframe.contentWindow.name; // 读取数据 alert(data); //弹出'I was there!' } else if (state === 0) { state = 1; iframe.contentWindow.location = "http://a.com/proxy.html"; // 设置的代理文件 } }; iframe.src = 'http://b.com/b.html'; if (iframe.attachEvent) { iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe); </script>
b.html <script type="text/javascript"> window.name = 'I was there!'; // 这里是要传输的数据,大小通常为2M,IE和firefox下能够大至32M左右 // 数据格式能够自定义,如json、字符串 </script>
proxy 是一个代理文件,空的就能够,须要和 a 在同一域下
在不一样的子域 + iframe交互的时候,获取到另一个 iframe 的 window对象是没有问题的,可是获取到的这个window的方法和属性大多数都是不能使用的。
这种现象能够借助document.domain
来解决。
example.com <iframe id='i' src="1.example.com" onload="do()"></iframe> <script> document.domain = 'example.com'; document.getElementById("i").contentWindow; </script>
1.example.com <script> document.domain = 'example.com'; </script>
这样,就能够解决问题了。值得注意的是:document.domain
的设置是有限制的,只能设置为页面自己或者更高一级的域名。
document.domain的后话:
利用这种方法是极其方便的,可是若是一个网站被攻击以后另一个网站极可能会引发安全漏洞。
这种方法能够把数据的变化显示在 url 的 hash 里面。可是因为 chrome 和 IE 不容许修改parent.location.hash 的值,因此须要再加一层。
a.html 和 b.html 进行数据交换。
a.html function startRequest(){ var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://2.com/b.html#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.html //模拟一个简单的参数处理操做 switch(location.hash){ case '#paramdo': callBack(); break; case '#paramset': //do something…… break; } function callBack(){ try { parent.location.hash = 'somedata'; } catch (e) { // ie、chrome的安全机制没法修改parent.location.hash, // 因此要利用一个中间域下的代理iframe var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://3.com/c.html#somedata'; // 注意该文件在"a.com"域下 document.body.appendChild(ifrproxy); } }
c.html //由于parent.parent和自身属于同一个域,因此能够改变其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1);
这样,利用中间的 c 层就能够用 hash 达到 a 与 b 的交互了。
这个方法是 HTML5 的一个新特性,能够用来向其余全部的window对象发送消息。须要注意的是咱们必需要保证全部的脚本执行完才发送MessageEvent,若是在函数执行的过程当中调用了他,就会让后面的函数超时没法执行。
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
http://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html
http://www.cnblogs.com/rainman/archive/2011/02/21/1960044.html
原文地址: https://github.com/rccoder/blog/issues/5
欢迎 star 仓库~