JSONP
原理:
动态创建一个script标签,动态加载一个js文件,载入成功之后会执行在url参数中指定的函数,并且把需要的json数据所谓参数传入;
优缺点:
只能传递get请求,有一定的限制
// 原生方法
(function (window,document) {
"use strict";
var jsonp = function (url,data,callback) {
// 1.将传入的data数据转化为url字符串形式
// {id:1,name:'zhangsan'} => id=1&name=zhangsan
var dataString = url.indexof('?') == -1? '?': '&';
for(var key in data){
dataString += key + '=' + data[key] + '&';
};
// 2 处理url中的回调函数
// cbFuncName回调函数的名字 :my_json_cb_名字的前缀 + 随机数(把小数点去掉)
var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');
dataString += 'callback=' + cbFuncName;
// 3.创建一个script标签并插入到页面中
var scriptEle = document.createElement('script');
scriptEle.src = url + dataString;
// 4.挂载回调函数
window[cbFuncName] = function (data) {
callback(data);
// 处理完回调函数的数据之后,删除jsonp的script标签
document.body.removeChild(scriptEle);
}
// 5.append到页面中
document.body.appendChild(scriptEle);
}
// 因为jsonp是一个私有函数外部不能调用,所有jsonp函数作为window对象的一个方法,供外部调用
window.$jsonp = jsonp;
})(window,document)
// 原生方式精简版
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.baidu.com/xx?a=b&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
// jQuery
$.ajax({
url: url,
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "callback", // 自定义回调函数名
data: {}
});
// 后端node.js代码
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...');
CROS跨域资源共享
原理:服务器端对于cros的支持,设置Access-Control-Allow-Origin为你传输的origin的值,浏览器监测到响应的设置,支持跨域
跨域携带cookies
// 原生
...
xhr.withCredentials = true
...
// jQuery
$.ajax({
...
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
...
});
// axios
axios.defaults.withCredentials=true; // 让ajax携带cookie
//服务端设置
const app = express()
app.all('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*') // 访问控制允许来源:所有
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept') // 访问控制允许报头 X-Requested-With: xhr请求
res.header('Access-Control-Allow-Metheds', 'PUT, POST, GET, DELETE, OPTIONS') // 访问控制允许方法
res.header('X-Powered-By', 'nodejs') // 自定义头信息,表示服务端用nodejs
res.header('Content-Type', 'application/json;charset=utf-8')
res.header('Access-Control-Max-Age', '600')
next()
})
优缺点:支持所有的请求类型,get、post、put、delete、updata等等
所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案
window.name + iframe跨域
原理:一般用于iframe的跨域问题,在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面的window.name都有读写的权限,并且是持久存在一个再如果的所有页面中
// 在A页面设置函数
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域页面
iframe.src = url;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = function() {
if (state === 1) {
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
state = 1;
}
};
document.body.appendChild(iframe);
// 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};
// 请求跨域b页面数据
proxy('http://www.xxx.com/B.html', function(data){
alert(data);
});
// 在B页面设置window.name值
<script>
window.name = 'B页面向A页面传递的数据';
</script>
优缺点:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。window.name支持非常长的name值(2MB)
H5属性 window.postMessage
原理:postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为”*”,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/”。
// A页面
<iframe id="iframe" src="http://www.B.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
msg: 'xxx'
};
// 向B传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.B.com');
};
// 接受B返回数据
window.addEventListener('message', function(e) {
alert('data from B ---> ' + e.data);
}, false);
</script>
// B页面
<script>
// 接收A的数据
window.addEventListener('message', function(e) {
alert('data from A ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回A
window.parent.postMessage(JSON.stringify(data), 'http://www.A.com');
}
}, false);
</script>
nginx代理跨域
nginx设置反向代理接口跨域(没研究)
nodejs中间件代理跨域
原理:node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发
node + vue + webpack-dev-server代理接口跨域,在开发模式下,只需要配置devServer=>proxy即可
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
target: 'http://www.B.com:8080', // 代理跨域目标接口
changeOrigin: true,
cookieDomainRewrite: 'www.A.com' // 可以为false,表示不修改
}],
noInfo: true
}
}
在非框架类时nodejs使用http-proxy-middleware
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({
// 代理跨域目标接口
target: 'http://www.B.com:8080',
changeOrigin: true,
// 修改响应头信息,实现跨域并允许带cookie
onProxyRes: function(proxyRes, req, res) {
res.header('Access-Control-Allow-Origin', 'http://www.A.com');
res.header('Access-Control-Allow-Credentials', 'true');
},
// 修改响应信息中的cookie域名
cookieDomainRewrite: 'www.A.com' // 可以为false,表示不修改
}));
app.listen(5566);
console.log('Proxy server is listen at port 5566...');