只要你做过前端项目,你就一定听过跨域。如果跨域都有没听说过的,还说自己是做前端的,那你肯定还停留在十年前的项目吧,JSP,PHP,ASP后端连着前端页面一手梭哈,那个时候到是真的不存在说跨域的问题,毕竟都是在后端,哪有什么跨域。虽然都听说过跨域,那你真的对跨域了解么,你又知道解决跨域常见的几种方案以及背后的原理么,接下来就让我们来一起看一看。
什么是跨域?
为什么说后端是不存在跨域的呢,因为跨域产生的原因是出于浏览器同源策略的限制,如果你想对同源策略有更加深的了解,可以点击下面的链接看一下https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
不想看或没完全看懂也不要紧,你只要知道,浏览器的同源策略导致了当你的URL中协议+主机+端口号有任何不同的时候,你都无法直接发送AJAX请求数据。当你这样做的时候,浏览器就会给你报下面的错误:
XMLHttpRequest cannot load http://localhost:8080/spring01/user/test. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:36813' is therefore not allowed access. The response had HTTP status code 405.
你说,那这个问题好解决,我直接把浏览器的同源策略给关闭不就行了么,有这样想法的人,直接”拉出去枪毙吧“,别人浏览器厂商辛辛苦苦折腾出来的这个东西保护自己的,你给它关了,那不搞事情么。比如你将谷歌浏览器的给关掉,就会给你下面的提示:
您使用的是不受支持的命令行标记: -disable web -security.稳定性和安全性会有所下降。
如果,你说我只是想把它先关掉,这样就能在自己电脑上做项目测试自己做的项目,等上线后,跨域的问题再交给后端来做不就好了么。那你如果非要这样做,也不是不行,只是你要承担一定的不安全因素,那还不如使用下面的一些方法来解决。
还有一些人会问,既然这样会有跨域,那我将我的web服务和数据服务器放在一起不就好了,在一个服务器上开一个端口,将静态html页面和后台api接口放在一起,不也就没有跨域的问题了么,其实,如果你做的是一个很小很小的项目的话,可以这样做。但是我相信现在的公司都不会这样做,都是会将web服务器和数据服务器分开的,甚至,有的还会分出来图片服务器和音视频服务器等,这样做的好处当然是很明显的,大大降低了服务器的压力,而且不同的服务器还能根据自身的特点进行不同的部署,比如一些html,js,图片等静态资源,就可以通过cdn部署到全国各地,根据用户的位置,请求最近的服务器,分散压力的同时速度上也会有一定的提升。
那既然,同源策略产生的跨域问题是无法直接给消除的,我们就看一看目前常规的一些解决方案吧!
jsonp
首先我们就要说一说jsonp的方式了,相信也有很多人用过,前面我们说了,同源策略导致无法直接发送AJAX请求数据,但是,我们知道网页里script标签内部请求资源是不受跨域的影响的(你没有说在用script标签请求js文件的时候遇到什么跨域的问题的吧,只要你路径对,就可以请求的到)。所以,解决方案就来了,在我们需要请求数据的时候,利用js动态的创建一个script标签,里面的src属性设置为你要请求的接口地址,同时传过去一个callback函数,后台将数据拼接到callback再传回浏览器,浏览器会执行返回过来的callback函数就能获取到数据了,下面是一个用原生js封装好的jsonp请求:
/*
@function 发送jsonp请求
@param url {String} 接口地址
@para params {Object} 传入的参数,会拼接到url上
@para success {Function} 成功获取数据后的回调函数
*/
export function jsonp({
url,
params = {},
success
}) {
// 根据时间戳生成一个callback名
let callbackName = 'jsonp_callback_' + Date.now() + Math.random().toString().substr(2, 5);
let script = document.createElement('script');
let baseUrl = `${url}?callback=${callbackName}`;
// 取出params对象属性并得到完整url
for (let item in params) {
baseUrl += `&${item}=${params[item]}`;
}
// jsonp核心,通过script的跨域特性发出请求
script.src = baseUrl;
// 把创建的script挂载到DOM
document.body.appendChild(script);
// 给window添加属性,用于获取jsonp结果
window[callbackName] = (res) => {
// 执行success回调
success(res);
// 删除window下属性
delete window[callbackName];
// 得到结果后删除创建的script
document.body.removeChild(script);
}
}
当然像jquery和vue里面也有自己封装好的jsonp请求的函数,这里就不再说了。毕竟现在使用jsonp方式比较少了,毕竟jsonp的方式只能实现get请求,而get请求,需要将参数拼接到url上,会有一定的风险,而且jsonp请求需要后台的支持,需要后台写的是jsonp接口,如果后台写的post接口,这种方式你就使用不成了。不过有些时候,还是要用到这个的,比如前一段我做的一个疫情可视化项目,数据来源就是腾讯提供的,它的接口就是jsonp格式的,你总没办法让腾讯为了你,去把接口给改了吧,这时候就只能乖乖的去使用jsonp方式请求了。
https://github.com/PigMerit/virusMap (这是我做的疫情图项目地址,里面有项目的效果展示,大家可以去点个赞哦)
iframe方式
这个方式听说是比较麻烦的,而且我也没使用过,就说一说大概的原理吧。比如页面A请求数据是跨域的,那么我就使用iframe标签(display设置为none)在页面里引用一个不跨域的页面B,用页面B去获取数据,然后再利用浏览器多页面之间数据的传递方式,将页面B请求到的数据传给页面A,问题也就解决了。如果你需要对这种方式有更多的了解,可以参考 https://segmentfault.com/a/1190000013536703
(好像不能冠名一篇文章理清了啊!)
CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing) CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能, 所以通过此方案解决问题,就需要和负责后端的人沟通一下了。
这种方式比较简单,后端只要设置一下浏览器响应的头信息就好了,以node为例:
app.all('*',function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');//设置方法
res.header('Access-Control-Allow-Credentials', false);
if (req.method == 'OPTIONS') {
res.send(200); // 意思是,在正常的请求之前,会发送一个验证,是否可以请求。
}
else {
next();
}
});
其中,主要设置的是“Access-Control-Allow-Origin”,它的值要么是允许发送请求的地址,要么是一个*( 表示接受任意域名的请求 )。
这种添加几行代码就解决的方式看起来还是很简单的,但是,如果你此时无法与后端人沟通又急着跨域看自己做的项目的成果的时候,就不是很适用了。而且设置“Access-Control-Allow-Origin”值为“*”又会和“Access-Control-Allow-Credentials”(设置是否允许携带cookie值)值为’true‘冲突,这里就不细谈这个问题了。
接下来你就要看到一种只用前端就能解决的方式了
proxy
proxy方式的本质原理是一种反向代理。我们已经知道,在后端请求数据是没有跨域的(要不然也不会有那么多爬虫程序了),那么,我们前端如果想向某个跨域的地址请求数据的时候,可以先在自己同源的后端请求到这个数据,然后再从后端拿这个数据就好了。(这个原理和iframe有点像,都是通过一个中间站中转一下,只是一个通过前端页面转,一个通过后端转)。
proxy就是已经将上面说的原理给实现了,你只要配置一下就好了。
每个框架都有自己的proxy配置方式,以vue举例,只需在vue.config.js配置以下信息即可:
devServer: {
proxy: {
'/api': { //如果你的地址 以/api 开头他就会请求到http://122.51.238.153
target: 'http:/ /122.51.238.153',
changeOrigin: true,
//WS:true, //如果要代理 websockets,配置这个参数
//secure: false, // 如果是https接口,需要配置这个参数
pathRewrite: {
'^/api':'' //请求的时候/api就会替换成'’
}
}
比如,此时,你用axios向"/api/getall"发送请求,就能得到"http://122.51.238.153/getall"接口的数据了。
当然,这个proxy的配置是在你的开发环境上做的,在你开发项目的时候都能够通过这个方式解决跨域问题,测试自己的项目,等到项目上线的时候,就交给后端人员配置一下服务器,做个nginx反向代理就妥妥的了。
看到这里,跨域对你来说,已经不再是问题了吧,如果还有问题,可以想办法联系我哦。