咱们试想一下如下几种状况:javascript
为了解决不一样域名相互访问数据致使的不安全问题,Netscape提出的一个著名的安全策略——同源策略,它是指同一个“源头”的数据能够自由访问,但不一样源的数据相互之间都不能访问。html
很明显,上述第1个和第3个例子中,不一样的天猫商店和 qq 空间属于同源,能够共享登陆信息。qq 为了区别不一样的 qq 的登陆信息,从新打开了一个窗口,由于浏览器的不一样窗口是不能共享信息的。而第2个例子中的支付宝、网银、不知名网站之间是非同源的,因此彼此之间没法访问信息,若是你执意想请求数据,会提示异常:前端
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
那么什么是同源的请求呢?同源请求要求被请求资源页面和发出请求页面知足3个相同:java
协议相同
host相同
端口相同
简单理解一下:node
/*如下两个数据非同源,由于协议不一样*/ http://www.abc123.com.cn/item/a.js https://www.abc123.com.cn/item/a.js /*如下两个数据非同源,由于域名不一样*/ http://www.abc123.com.cn/item/a.js http://www.abc123.com/item/a.js /*如下两个数据非同源,由于主机名不一样*/ http://www.abc123.com.cn/item/a.js http://item.abc123.com.cn/item/a.js /*如下两个数据非同源,由于协议不一样*/ http://www.abc123.com.cn/item/a.js http://www.abc123.com.cn:8080/item/a.js /* 如下两个数据非同源,域名和 ip 视为不一样源 * 这里应注意,ip和域名替换同样不是同源的 * 假设www.abc123.com.cn解析后的 ip 是 195.155.200.134 */ http://www.abc123.com.cn/ http://195.155.200.134/ /*如下两个数据同源*/ /* 这个是同源的*/ http://www.abc123.com.cn/source/a.html http://www.abc123.com.cn/item/b.js
http 请求知足一下条件时称为简单请求,不然是非简单请求:webpack
HTTP的头信息不超出如下几种字段:web
application/x-www-form-urlencoded
, multipart/form-data
, text/plain
非简单请求在发送以前会发送一次 OPTION 预请求,若是在跨域操做遇到返回 405(Method Not Allowed) 错误,须要服务端容许 OPTION 请求。shell
适用条件:请求的 GET 接口须要支持 jsonp 访问
这里须要强调的是,jsonp 不属于 Ajax 的部分,它只是把 url 放入 script 标签中实现的数据传输,不受同源策略限制。因为通常库也会把它和 Ajax 封装在一块儿,因为其和 Ajax 根部不是一回事,因此这里不讨论。下面是一个 jsonp 的例子:express
window.jsonpCallback = console.log; var JSONP = document.createElement("script"); JSONP.src = "http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13122222222&t=" + Math.random() + "&callback=jsonpCallback";; document.body.appendChild(JSONP);
后端支持jsonp方式(Nodejs)npm
var querystring = require('querystring'); var http = require('http'); var server = http.createServer(); server.on('request', function(req, res) { var params = qs.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回设置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
document.domain
适用条件: host 中仅服务器不一样的状况,域名自己应该相同
www.dom.com
和 w1.dom.com
须要同源才能访问,能够将 document.domain 设置为 dom.com
解决该问题
document.domain = 'dom.com';
例如,我想开发一个浏览器插件,发现腾讯视频页有个 iframe 其自己的跨域的,没法获取其 iframe 的 DOM 对象。但域名部分相同,能够经过该方法解决.
注:若是你想设置它为彻底不一样的域名,那确定会报同源错误的,注意使用范围!
适用条件: host 中仅服务器不一样的状况,域名自己应该相同
有了上面的例子就不难理解这个方法了,严格来讲这不是一个新的方法,而是上一个方法的延伸。经过设置document.domain
, 使同一个域名下不一样服务器名的页面能够访问数据,但值得注意的是:这个数据访问不是相互的,外部页面能够访问 iframe 内部的数据,但 iframe 没法不能访问外部的数据。
适用条件:iframe 和其宿主页面通讯
一个完成的 url 中 # 及后面的部分为 hash, 能够经过修改这个部分完成iframe 的和宿主直接的数据传递,下面演示一下 iframe 页面(B.html)像宿主(A.html)传数据, 反之同理:
// A.html data = ['book', 'map', 'shelf', 'knife']; setTimeout(() => { location.hash = window.encodeURIComponent(data.join('/')); }, 1000); // B.html window.parent.onhashchange = function (e) { var data = window.decodeURIComponent(e.newURL.split('#')[1]).split('/'); console.log(data); // ["book", "map", "shelf", "knife"] }
*注意反向传递数据时应该使用 window.parent.location.hash
window.name
适用条件:宿主页面和 iframe 之间通讯
window对象有个name属性,该属性有个特征:即在 window 的生命周期内,窗口载入的全部的页面 (iframe) 都是共享一个 window.name
的,每一个页面对 window.name
都有读写的权限,window.name
是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。
这样在 window 中编辑 window.name 就能够在 iframe 中获得,但这个过程缺少监听,宿主页面(A.html)和 iframe 页面(B.html)相互并不知道对方在何时修改该值:
// A.html setTimeout(() => { window.parent.name = "what!"; }, 2000); // B.html setTimeout(() => { console.log(window.name); // what! }, 2500);
postMessage
适用条件:postMessage 是 H5 提出的一个消息互通的机制,解决 iframe 不能消息互通的问题,也能够跨 window 通讯,语法以下:
// 在 www.siteA.com 中发出消息 // @message{any} 要发送的数据(注意:老版本浏览器只支持字符串类型) // @targetOrigin{string} 规定接收数据的域,只有其指定的域才能收到消息,若是为"*"则没用域的限制 // transfer{any} 与 message 一同发送并转移全部权 window.postMessage(message, targetOrigin, [transfer]); // 在另外一个页面接受参数 window.onmessage = console.log;
这里暂不谈论第三个参数,由于你可能一生也用不到它。而 targetOrigin 最好不要使用 "*",除非你想让全部页面都收到你的消息。
一种你会用到的场景(iframe):
<!-- www.siteA.com/index.html --> <script> window.addEventListener('message', function(e){ console.log('Get message: "' + e.data.title + '" from ' + e.origin); // 'Get message: "Saying hello to siteA!" from http://www.siteB.com' }); </script> <iframe src="http://www.siteB.com"></iframe> <!-- www.siteB.com/index.html --> <script> function sendMessage(){ window.postMessage({title: 'Saying hello to siteA!'}, 'http://www.siteA.com'); } setTimeout(sendMessage, 2000); </script>
这一种仅仅是没有了iframe,当你在同一个浏览器窗口同时打开 www.siteA.com
和 www.siteB.com
两个标签时也能够这样用
<!-- www.siteA.com/index.html --> <script> window.addEventListener('message', function(e){ console.log('Get message: "' + e.data.title + '" from ' + e.origin); // 'Get message: "Saying hello to siteA!" from http://www.siteB.com' }); </script> <!-- www.siteB.com/index.html --> <script> function sendMessage(){ window.postMessage({title: 'Saying hello to siteA!'}, 'http://www.siteA.com'); } setTimeout(sendMessage, 2000); </script>
页面须要访问一些跨域接口,因为代理的存在,在服务器看来请求是不跨域,因此使用各类请求。但须要注意 http 到 https 的兼容问题。
好比当我在一些在线平台开发网站后获得一个页面 www.site-A.com
, 而这个页面须要请求我本身的数据服务器data.site-B.com
上的数据, 这样一样会产生跨域问题,可是www.site-A.com
这个页面是挂在第三方服务器上的,解决这个问题能够采用代理服务器的方法:
var express = require('express'); var request = require('request'); var app = express(); app.use('/api', function(req, res) { var url = 'http://data.site-B.com/api2' + req.url; req.pipe(request(url)).pipe(res); }); app.use('/', function(req, res) { var url = 'http://data.site-C.com'; req.pipe(request(url)).pipe(res); });
固然还须要同时配置一个 host:
127.0.0.1 local.www.site-B.com
而后访问 local.www.site-B.com 就 OK 了。
适用条件:CORS 须要服务端支持,且存在必定的兼容性问题(现在你已经能够不考虑,但必要时不要忘了这个'bug')。其经过添加 http 头关键字实现跨域可访问,包括以下头内容:
# www.siteA.com/api 返回相应须要具备以下 http 头字段 Access-Control-Allow-Origin: 'http://www.siteB.com' # 指定域能够请求,通配符'*'(必须) Access-Control-Allow-Methods: 'GET,PUT,POST,DELETE' # 指定容许的跨域请求方式(必须) Access-Control-Allow-Headers: 'Content-Type' # 请求中必须包含的 http 头字段 Access-Control-Allow-Credentials: true # 配合请求中的 withCredentials 头进行请求验证
经过 express 实现也很简单,在注册路由以前添加:
var cors = require('cors'); // 经过 npm 安装 app.use(cors());
固然你也能够自定义一个中间件:
// 自定义中间件 var cors = function (req, res, next) { // 自定义设置跨域须要的响应头。 res.header('Access-Control-Allow-Origin', 'http://www.siteB.com'); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); next(); }; app.use(cors); // 运用跨域的中间件
ws 协议是 H5 中的 web 全双工通讯解决方案,常规 http 属于请求相应的过程,在客户端没有请求的状况下,服务端没法给客户端主动推送数据,ws 协议解决了这个问题,但处于安全考虑,其一样有同源策略的限制。
*这里不讨论经过长链接和服务端挂起请求等方法推送数据,本文只讨论跨域。
下面举个例子(依赖socket.io.js):
// 前端部分 socket.on('connect', function() { // 监听服务端消息 socket.on('message', function(msg) { console.log('data from server: ' + msg); }); // 监听服务端关闭 socket.on('disconnect', function() { console.log('Server socket has closed.'); }); }); document.getElementById('input').onkeyup = function(e) { if(!e.shiftKey && !e.ctrlKey && !e.altKey && e.keyCode === 13) socket.send(this.value); }; // 后端部分(node.js) var http = require('http'); var socket = require('socket.io'); // 启http服务 var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); // 监听socket链接 socket.listen(server).on('connection', function(client) { // 监听客户端信息 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ' + msg); }); // 监听客户端断开 client.on('disconnect', function() { console.log('Client socket has closed.'); }); });
HTML 中 <img>
, <video>
和 <script>
具备 crossorigin 属性。添加属性会使相应添加 CORS 相关 http 头(须要服务器支持)。同时,其还有如下可能的取值:
当只写了 crossorigin 属性没有指定值时,其默认值为 "anonymous"。即如下两行代码等价:
<scirpt src="a.com/vendor.js" corssorigin></script> <scirpt src="a.com/vendor.js" corssorigin="anonymous"></script>
方法 | 使用条件 | 使用条件是否与后端交互 | 优势 | 缺点 | |
---|---|---|---|---|---|
JSONP | 服务端支持 jsonp 请求 | 是 | 兼容全部浏览器 | 只支持 GET 请求,只能和服务端通讯 | |
CORS | 服务器相应须要相关投资端支持 | 是 | 方便的错误处理,支持全部http请求类型 | 存在浏览器兼容性问题(现在能够忽略了) | |
document.domain |
仅须要跨子域发起请求 | 是 | 使用便捷,没有兼容问题 | 对于彻底不一样的域名没法使用 | |
postMessage |
浏览器不一样 window 间通讯、 iframe 和其宿主通讯 | 否 | 支持浏览器页面间或页面和 iframe 间同行 | 须要浏览器兼容 H5 接口 | |
window.name |
iframe 和其宿主通讯 | 否 | 简单易操做 | 数据暴露在全局不安全 | |
location.hash |
iframe 和其宿主通讯 | 否 | 简单易操做 | 数据在 url 中不安全而且有长度限制 | |
反向代理 | - | 是 | 任何状况均可用 | 使用比较麻烦,须要本身创建服务 |
添加 webpack 配置以下:
const config = { // ... devServer: { // ... proxy: { '/api': { target: 'https://data.site-B.com/api2', changeOrigin: true, // 容许跨域 secure: false // 容许访问 https }, '/': { target: 'https://data.site-C.com', changeOrigin: true, secure: false }, } } }; module.exports = config;
location / { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods: GET,PUT,POST,DELETE; }
server { listen 7001; server_name www.domain1.com; location / { proxy_pass http://www.B.com:7001; #反向代理 } }