文章列出解决方案以及对应的demo, 拒绝说概念,不在稀里糊涂。
同一个 origin 下,父页面能够经过 iframe.contentWindow 直接访问 iframe 的全局变量、DOM 树等,iframe 能够也经过 parent/top 对父页面作一样的事情。 javascript
domain.htmlhtml
<body> <iframe id="ifr" src="http://b.tblog.com:3004/domain2.html"></iframe> <script> document.domain = 'tblog.com'; function aa(str) { console.log(str); } window.onload = function () { document.querySelector('#ifr').contentWindow.bb('aaa'); } </script>
domain2.htmlhtml5
<body> 2222222222 <script> document.domain = 'tblog.com'; function bb(str) { console.log(str); } parent.aa('bbb'); </script> </body>
完整demojava
html5新增API, 支持IE8+。node
otherWindow.postMessage(message, targetOrigin, [transfer]);
传递过来的message的属性有:git
下面index.html和index2.html通讯
index.htmlgithub
<body> <input type="text" placeholder="http://b.tblog.com:3004/index2.html"> <iframe src="http://192.168.101.5: 3004/index2.html" frameborder="0"></iframe> <script> const input = document.querySelector('input'); input.addEventListener('input', function () { window.frames[0].postMessage(this.value, '*'); // window.frames[0].postMessage(this.value, 'http://192.168.101.5'); // window.frames[0].postMessage(this.value, 'http://192.168.101.5:3004'); }); // 接收消息 window.addEventListener('message', function (e) { input.value = e.data; console.log('父窗口', e.data); console.log('父窗口', e.source); console.log('父窗口', e.origin); }); </script> </body>
index2.htmljson
<body> 子窗口 <input id="input" type="text" placeholder="http://a.tblog.com:3004/index.html"> <script> const input = document.querySelector('#input'); input.addEventListener('input', function () { window.parent.postMessage(this.value, '*'); }); // 接收消息 window.addEventListener('message', function (e) { input.value = e.data; console.log('子窗口', e.data); console.log('子窗口', e.source); console.log('子窗口', e.origin); }); </script> </body>
完整democanvas
原理是利用location.hash来进行传值。改变hash并不会致使页面刷新,因此能够利用hash值来进行数据传递,固然数据容量是有限的。
例如:假设a.tblog.com:3004 和 192.168.101.5:3004/index2.html
通讯
原理:a.tblog.com:3004中index.html以iframe将192.168.101.5:3004/index2.html
页面引入,在192.168.101.5:3004/index2.html
中插入新的iframe, 此iframe引入的页面和a.tblog.com:3004同源,就可将192.168.101.5:3004/index2.html
的hash数据传入a.tblog.com:3004页面的hash值中。parent.parent.location.hash = self.location.hash.substring(1);
a.tblog.com:3004/index.htmlsegmentfault
<script> var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://192.168.101.5:3004/ index2.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); </script>
192.168.101.5:3004/ index2.html
<body> <script> //模拟一个简单的参数处理操做 switch (location.hash) { case '#paramdo': callBack(); break; case '#paramset': //do something…… break; } function callBack() { try { parent.location.hash = 'somedata'; } catch (e) { var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://a.tblog.com:3004/index3.html#somedata'; // 注意该文件在"a.com"域下 document.body.appendChild(ifrproxy); } } </script> </body>
a.tblog.com:3004/index3.html
<body> <script> //由于parent.parent和自身属于同一个域,因此能够改变其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1); </script> </body>
完整demo
window.name 获取/设置窗口的名称。
窗口的名字主要用于为超连接和表单设置目标(targets)。窗口不须要有名称。
window.name属性可设置或者返回存放窗口名称的一个字符串, name值在不一样页面或者不一样域下加载后依旧存在,没有修改就不会发生变化,而且能够存储很是长的name(2MB)。
场景1 - 同源
a.html
<body> <script type="text/javascript"> const iframe = document.createElement('iframe'); iframe.src = 'http://a.tblog.com:3004/b.html'; iframe.style.display = 'none'; document.body.appendChild(iframe); iframe.onload = function () { console.log(iframe.contentWindow.name) }; </script> </body>
b.html
<body> <script> window.name = '子页面的数据'; </script> </body>
场景2 - 不一样源
利用iframe中window.name在不一样页面或者不一样域下加载后依旧存在的特性。a.tblog.com:3004/a.html
中经过iframe添加192.168.0.103:3004/b.html
(数据页面, 指定window.name 的值),监听iframe的load, 改变iframe的src与a.tblog.com:3004/a.html
同源代理页面a.tblog.com:3004/c.html
(空页面)。
a.tblog.com:3004/a.html
const iframe = document.createElement('iframe'); iframe.style.display = 'none'; let state = 0; iframe.onload = function () { console.log('iframe.onload', state, iframe.contentWindow); if (state === 1) { const data = JSON.parse(iframe.contentWindow.name); console.log(data, state); iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if (state === 0) { state = 1; console.log('数据', window.name) iframe.contentWindow.location = 'http://a.tblog.com:3004/c.html'; } }; iframe.src = 'http://192.168.0.103:3004/b.html'; document.body.appendChild(iframe);
完整demo
jsonp原理:
因为使用script标签的src属性,所以只支持get方法
客户端代码
<body> <button class="get">get data</button> <script> const btn = document.querySelector('.get'); btn.addEventListener('click', function () { const script = document.createElement('script'); script.setAttribute('src', 'http://127.0.0.1:8080/getNews?callback=getData'); document.head.appendChild(script); document.head.removeChild(script); }) function getData(news) { console.log(news) } </script> </body>
服务端代码
const http = require('http'); const fs = require('fs'); const path = require('path'); const url = require('url'); http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/getNews': const news = [{id: 678}]; res.setHeader('Content-type', 'text/json; charset=utf-8'); if(pathObj.query.callback){ res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')'); }else { res.end(JSON.stringify(news)); } break; default: res.writeHead(404, 'not found'); } }).listen(8080);
完整demo
原理
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不一样源服务器上的指定的资源。跨域资源共享( CORS )机制容许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。
什么状况下须要CORS
功能概述
跨域资源共享标准新增了一组 HTTP 首部字段,容许服务器声明哪些源站经过浏览器有权限访问哪些资源。容许服务器声明哪些源站经过浏览器有权限访问哪些资源。对于get之外的请求,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否容许该跨域请求。服务器确认容许以后,才发起实际的 HTTP 请求。 真个过程浏览器自动完成,服务器会添加一些附加的头信息, 所以,实现CORS通讯的关键是服务器。只要服务器实现了CORS接口,就能够跨源通讯。
简单请求
某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。只要同时知足如下两大条件,就属于简单请求:
(1) 请求方法是如下三种方法之一: HEAD GET POST (2)HTTP的头信息不超出如下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
请求响应结果多出的字段:
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: <origin> | *
; 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求, 有次响应头字段就能够跨域
Access-Control-Allow-Credentials: true
; 当浏览器的credentials设置为true时, 此响应头表示是否容许浏览器读取response的内容,返回true则能够,其余值均不能够,Credentials能够是 cookies, authorization headers 或 TLS client certificates。
Access-Control-Allow-Credentials 头 工做中与XMLHttpRequest.withCredentials 或Fetch API中的Request() 构造器中的credentials 选项结合使用。Credentials必须在先后端都被配置(即the Access-Control-Allow-Credentials header 和 XHR 或Fetch request中都要配置)才能使带credentials的CORS请求成功。 若是withCredentials 为false,服务器赞成发送Cookie,浏览器也不会发送,或者,服务器要求设置Cookie,浏览器也不会处理。
须要注意的是,若是要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其余域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也没法读取服务器域名下的Cookie。
// 容许credentials: Access-Control-Allow-Credentials: true // 使用带credentials的 XHR : var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://example.com/', true); xhr.withCredentials = true; xhr.send(null); // 使用带credentials的 Fetch : fetch(url, { credentials: 'include' })
在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma, 若是想拿到其余字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')能够返回FooBar字段的值。
代码以下:
<!-- 服务端 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': const news = [{id: 678}]; res.setHeader('Content-type', 'text/json; charset=utf-8'); res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // res.setHeader('Access-Control-Allow-Origin', '*'); // 须要cookie等凭证是必须 res.setHeader('Access-Control-Allow-Credentials', true); res.end(JSON.stringify(news)); break; default: res.writeHead(404, 'not found'); } }).listen(8080, (err) => { if (!err) { console.log('8080已启动'); } }); <!-- 客户端 --> <body> <script> const xhr = new XMLHttpRequest(); xhr.open('GET', 'http://localhost:8080/user', true); // 须要cookie等凭证是必须 xhr.withCredentials = true; xhr.onreadystatechange = (e) => { console.log('onreadystatechange', e) } xhr.send(); </script> </body>
完整demo
非简单请求
非简单请求的CORS请求,会在正式通讯以前,增长一次HTTP查询请求,称为"预检"请求(preflight)。
以获知服务器是否容许该实际请求。"预检请求“的使用,能够避免跨域请求对服务器的用户数据产生未预期的影响。
当请求知足下述任一条件时,即应首先发送预检请求:
使用了下面任一 HTTP 方法:
人为设置了对cors安全首部字段集合外的其余首部字段, 该集合为:
Content-Type的值不属于下列之一:
以下是一个须要执行预检请求的 HTTP 请求:
<body> <script> const invocation = new XMLHttpRequest(); const url = 'http://localhost:8080/user'; const body = JSON.stringify({ name: 'toringo' }); function callOtherDomain() { if (invocation) { invocation.open('POST', url, true); invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/json'); invocation.onreadystatechange = (e) => { console.log('onreadystatechange', e) }; invocation.send(body); } } callOtherDomain(); </script> </body> <!-- 服务端 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': const news = {id: 678}; res.setHeader('Content-type', 'text/json; charset=utf-8'); res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // res.setHeader('Access-Control-Allow-Origin', '*'); // 须要cookie等凭证是必须 res.setHeader('Access-Control-Allow-Credentials', true); res.end(JSON.stringify(news)); break; default: res.writeHead(404, 'not found'); } }).listen(8080, (err) => { if (!err) { console.log('8080已启动'); } });
浏览器请求结果:
cors2.html:1 Access to XMLHttpRequest at 'http://localhost:8080/user' from origin 'http://127.0.0.1:3004' has been blocked by CORS policy: Request header field x-pingother is not allowed by Access-Control-Allow-Headers in preflight response.
如图所示发起了预检请求,请求头部多了两个字段:
Access-Control-Request-Method: POST; // 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法. Access-Control-Request-Headers: Content-Type, X-PINGOTHER; 告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHER 与 Content-Type。服务器据此决定,该实际请求是否被容许。
上例须要成功响应数据,服务端须要赞成:
http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': const news = {id: 678}; res.setHeader('Content-type', 'text/json; charset=utf-8'); res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // 新增的 res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'X-PINGOTHER, Content-Type'); res.setHeader('Access-Control-Max-Age', 86400); res.end(JSON.stringify(news)); break; default: res.writeHead(404, 'not found'); } }).listen(8080, (err) => { if (!err) { console.log('8080已启动'); } });
服务段新增的字段:
Access-Control-Allow-Origin: req.headers.origin Access-Control-Allow-Methods: POST, GET, OPTIONS // 代表服务器容许客户端使用 POST, GET 和 OPTIONS 方法发起请求。该字段与 HTTP/1.1 Allow: response header 相似,但仅限于在须要访问控制的场景中使用。这是为了不屡次"预检"请求。 Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 若是浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,代表服务器支持的全部头信息字段,不限于浏览器在"预检"中请求的字段。 Access-Control-Max-Age: 86400 // 代表该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,若是该首部字段的值超过了最大有效时间,将不会生效。
node中间件实现跨域代理,是经过一个代理服务器,实现数据的转发,也能够经过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
原理:服务器之间数据请求不存在跨域限制(同源策略是浏览器行为), 因此先将请求代理到代理服务器, 代理服务器在内部请求真实的服务器获得结果后end链接。
<!-- 服务 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); console.log('server', pathObj.pathname) switch(pathObj.pathname){ case '/user': const news = {id: 678}; res.end(JSON.stringify(news)); break; default: res.setHeader('Content-type', 'text/json; charset=utf-8'); res.end('未知错误'); } }).listen(4000, (err) => { if (!err) { console.log('4000已启动'); } }); <!-- 代理 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': res.setHeader('Content-type', 'text/json; charset=utf-8'); res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'X-PINGOTHER, Content-Type', }); console.log('proxy', req.method, pathObj.pathname); // 请求真实服务器 const proxyRequest = http.request({ host: '127.0.0.1', port: 4000, url: '/', path: pathObj.pathname, method: req.method, headers: req.headers }, (proxyRes) => { let body = ''; proxyRes.on('data', (chunk) => { body += chunk; }); proxyRes.on('end', () => { console.log('响应的数据 ' + body ); res.end(body); }) }).end(); break; default: res.writeHead(404, 'not found'); res.end(body); break; } }).listen(8080, (err) => { if (!err) { console.log('8080已启动'); } }); <!-- 客户端 index.html --> <body> <script> const invocation = new XMLHttpRequest(); const url = 'http://localhost:8080/user'; const body = JSON.stringify({ name: 'toringo' }); function callOtherDomain() { if (invocation) { invocation.open('POST', url, true); // invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/json'); invocation.onreadystatechange = (e) => { console.log('onreadystatechange', e) }; invocation.send(body); } } callOtherDomain(); </script> </body>
注意:
服务器和浏览器数据交互也须要遵循同源策略
-- 持续更新 --
Tips:
代码地址。~ github
WeChat
参考文章
https://developer.mozilla.org...
http://vinc.top/2017/02/09/%E...
http://www.ruanyifeng.com/blo...
https://segmentfault.com/a/11...
https://developer.mozilla.org...
https://developer.mozilla.org...
http://www.ruanyifeng.com/blo...