跨域产生的原因:
浏览器的同源策略,同源策略是浏览器最核心也是最基本的安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
跨域产生的判断:
当前页面的URL地址 和 请求数据接口的地址: 如果 协议、域名、端口号 都一致,则为同源策略; 三者中只要有一个不一样,就是跨域请求
不受同源策略限制的:
- 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
- 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的script,img,link,iframe等。
真实项目中,其实跨域策略比同源策略还要多一些:
-
现在的项目一般都是前后端分离,所以项目部署的时候,一般也是分开部署的(不排除后期有部署在一起的) => 有些公司需要web服务器由前端开发部署 linux + nginx...
-
为了保证服务器资源的合理利用,我们一般把服务器分成几类(一个项目,它访问的资源是分门别类在不同服务器上的)
- WEB服务器(页面、css、js等资源)
- 图片服务器
- 音视频服务器
- 数据服务器 ...
-
一个项目太大,我们分散成为很多子项目(基于二级域名分别部署),但是所有的子项目之间的数据需要互相访问(内部联调),此时也是以跨域为主
跨域请求的解决方案:
很早有这样一种方案(开发环境下):
项目部署的时候肯定会在一起(项目一旦部署不存在跨域访问),但是在开发的时候,开发者需要让本地的项目也能访问到正常的数据接口
1)最开始需要前端开发把后台代码也在本地部署起来,和本地的WEB部署在一起,在本地也产生同源的环境
缺点:需要前端随时同步后台的代码到本地,而且本地也要安装一些后台的环境
2)本地无需拿后台代码等,本地启动一个WEB服务,通过修改本地的HOST文件,模拟出和数据服务器相同的环境 xampp / wampp
其他方案总结:
- JSONP
- 其它一些方案(IFRAME)
- window.name
- window.location.hash
- document.domain 处理主域相同,子域不同的相互请求
- postMessage(H5)
- CORS 跨域资源共享
- 原理:让服务器端允许客户端跨域AJAX请求(基本上靠服务端设置允许跨域,客户端无需做太多的修改,直接正常发送AJAX请求即可)
- webpack的崛起,带动了http proxy这种方案的兴起
- 原理:利用webpack-dev-server插件,构建本地服务AA(预览本地开发的项目),我们发送AJAX请求,都先把请求发送给AA,有AA帮我们在发送给真正的服务器,AA起了一个中间代理的作用,从而解决跨域的问题!
- 缺点:但是上述操作是基于AA这个服务的(webpack-dev-server),项目最后部署的时候,没有webpack-dev-server插件,此时我们需要基于其它方案(例如:nginx反向代理)实现出当初AA服务代理的作用才可以!
- nginx反向代理
- web scoket
一、 JSONP:利用了SCRIPT(IMG/IFRAME)等标签不存在跨域请求限制的特点,实现跨域请求的
- 只能是GET请求(不能处理POST请求)
- =>传递给服务器的信息都是基于问号传参的方式传递过去的
- =>为了能够拿到服务器返回的结果,需要利用回调函数的机制,把客户端的某个函数(全局)名传递给服务器,由服务器接收到函数后进行特殊的处理 ?callback=func (callback这个名字是和服务器商定好的,一般都叫callback这个名字)
- JSONP的服务器端处理:所有的JSONP请求都需要服务器端接收到请求后做特殊处理
// 页面:
function func(result) { // 全局函数
console.log(result);
// result => {code: 0, codeText: 'SUCCESS', text: 'LX:1 === NAME:zf'}
}
// 请求结束,客户端拿到一个这样的结果:func({....}),浏览器会把函数执行(之所以这个函数在传递的时候需要是全局函数,因为后期获取到结果的时候,需要把它执行,只有全局函数才能在任何地方获取到)
let script = document.createElement('script');
script.src = `http://127.0.0.1:1001/test?lx=1&name=zf&callback=func&_=${new Date().getTime()}`;
document.body.appendChild(script);
// 后台(node):
app.get('/test', (req, res) => {
let {
lx,
name,
callback //callback存储的就是客户端传递过来的函数名
} = req.query;
let data = {
code: 0,
codeText: 'SUCCESS',
text: `LX:${lx} === NAME:${name}`
};
//特殊处理:把客户端传递的函数名和需要给客户端的数据,拼成这样的格式 “函数(数据)”
res.send(`${callback}(${JSON.stringify(data)})`);
});
二、 iframe的window.name
A(父) 嵌套 B(子)只能通过window.name传递数据,取数据,无法获取B里面的dom元素
A页面(WEB):http://127.0.0.1:1001/NAME/A.html
B页面(API):http://127.0.0.1:1002/NAME/B.html
proxy:临时过渡页面,不需要做任何处理,需与A相同的源下
// A页面:
let count = 0;
let iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'http://127.0.0.1:1002/NAME/B.html';
iframe.onload = function () {
// 想要正常拿到B中的内容,还需要把IFRAME的指向重新指回和A相同的源下
// proxy临时过渡页面,不需要做任何的处理
if (count === 0) {
iframe.src = '/NAME/proxy.html';
count++;
return;
}
// iframe.contentWindow.document获取的是proxy页面的dom元素,而非 B页面
console.log(iframe.contentWindow.name); // {code:0, codeText:'MESSAGE', data:'传递的信息'}
};
document.body.appendChild(iframe);
// B页面:
// 服务器端需要返回给A的信息都在window.name中存储着
window.name = JSON.stringify({
code: 0,
codeText: 'MESSAGE',
data: '传递的信息'
});
三、window.postMessage()
MDN链接:developer.mozilla.org/zh-CN/docs/…
- otherWindow.postMessage(message, targetOrigin, [transfer]);分发一个messageEvent消息; 将消息事件对象暴露给接收消息的窗口
targetOrigin: 指定哪些窗口可以接收到消息事件;① *:无限制,将导致数据泄露(不推荐) ② 确切的targetOrigin- window.addEventListener("message", receiveMessage, false);监听分发的message
receiveMessage函数 ev.data其他窗口传递过来的对象 ev.source发送消息事件的窗口对象
// A.html
<iframe id="iframe" src="http://127.0.0.1:1002/MESSAGE/B.html" frameborder="0" style="display: none;"></iframe>
// 向服务器端B发送请求
iframe.onload = function () {
// 传递给对应的服务器内容 => '传递的信息'
iframe.contentWindow.postMessage('传递的信息', 'http://127.0.0.1:1002/');
};
//=>监听服务端B返回的信息
window.onmessage = function (ev) {
console.log(ev.data); // {code:0, codeText:'OK',data:'传递的信息@@@'}
};
// B.html
// 监听客户端A发送过来的请求信息,把数据返回给客户端A
window.onmessage = function (ev) {
console.log(ev,'B');
//=>ev.data:客户端A发送的信息
//=>ev.source:客户端A
ev.source.postMessage(JSON.stringify({
code: 0,
codeText: 'OK',
data: ev.data + '@@@'
}), '*');
};
四、CORS跨域资源共享
MDN链接:developer.mozilla.org/zh-CN/docs/…
1.允许的源ORIGIN有两种写法:
- "*" :允许所有客户端发送请求,但是不能携带资源凭证(Credentials===false)
- "xxx":想要携带资源凭证,只能指定一个源,不能指定多个
2.POST请求中,在请求发送前,一般都会先发送一个OPTIONS试探请求(预检请求 preflight request),目的是验证,当前请求能否和服务器端建立链接,能链接上在发正式请求
// node后台
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD");
req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
});
五、webpack的devServer插件(仅适用于开发环境, 项目部署时没有该插件)
// webpack.config.js/vue.config.js:
module.export = {
devServer: {
post: 1001, // 项目的端口为1001
proxy: { // 启动代理
'/api': { // 匹配http://127.0.0.1:1002的所有/api接口
target: 'http://127.0.0.1:1002', //服务器的地址
sercure: false, //若为true则表示以https开头
pathRewrite: {'^/api': ''}, //重写地址,正则匹配,把请求地址/api/user的/api去掉,实际接口为/user
changeOrigin: true //把请求头当中的host改成服务器的地址
}
}
}
}
// 页面请求
// 请求路径必须是相对路径,不能是绝对路径
axios.get('/api/user').then(res=>{})
六、gulp的http-proxy-middleware插件(跟webpack的devServer插件一样,仅适用于开发环境)
// package.json代码:
{
"devDependencies": {
"gulp": "^4.0.2",
"gulp-connect": "^5.7.0",
"http-proxy-middleware": "^0.19.1"
}
}
// gulpfile.js代码:
var gulp = require('gulp'),
connect = require('gulp-connect'),
proxy = require('http-proxy-middleware');
//代理
gulp.task('server', function() {
connect.server({
livereload: true,
port: 1001,
open: true,
root: './',
host: '0.0.0.0',
middleware: function(connect, opt) {
return [
proxy('/api', {
target: 'http://127.0.0.1:1002',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
})
]
}
})
});
七、nginx服务器反向代理
参考资料:
www.cnblogs.com/ysocean/p/9…
segmentfault.com/a/119000001…
www.php.cn/nginx/
反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。
nginx 和 Apache 一样是服务器
项目启动(nginx服务器):http://localhost:8060/
接口:http://127.0.0.1:1002/api/test
// nginx.config
http {
server {
listen 8060; // nginx启动的端口,
server_name localhost; // nginx启动的域名
location / {
root "F:/example"; // 项目的文件路径
add_header Access-Control-Allow-Origin *; // 接受所有请求源
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; // 发送预检请求(preflight request)需要用到 OPTIONS方法
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
}
location /api { // 匹配所有/api的接口
// 被代理服务器的地址(即接口地址,若项目有端口号,不要忘记加端口号)
proxy_pass http://127.0.0.1:1002;
}
}
}
// 页面请求
// 请求路径必须是相对路径,不能是绝对路径
axios.get('/test').then(res => {
console.log(res)
})
请求的信息:
八、谷歌浏览器(仅适用于开发环境)
在浏览器的属性目标后面设置--args --disable-web-security --user-data-dir,路径后面要有个空格
谷歌更新到最新版本(81)后disable-web-security --user-data-dir失效,最新版本的谷歌 --user-data-dir 需要携带参数 就是打开当前目录的路径,通过加上--args --disable-web-security --user-data-dir="/tmp/chromedevtest"即可