最近在做一个全新的Vue
前端项目搭建及其开发工作,后端和前端都是分离的,所以避免不了开发环境和生产环境的跨域问题。 开发环境或者是生产环境,前端和后端都是在同一个机器下面部署或者是使用不同的端口号。 当我们的前端资源访问后端服务时得不到数据或没有达到预期的效果。以前也是知道跨域问题的, 但是没有好好总结。那么这篇文章就主要来讲讲遇到的跨域问题。以及如何解决,在 vue 项目中如何解决等。
跨域的问题
上面我们也说了, 现在开发项目大部分都是前后端分离的,那么无论是什么环境就肯定会遇到跨域问题。我们举一个例子来说明:
后端部分
我们使用 express
来弄一个后端的服务:
1 | mkdir serve |
1 | cd serve |
1 | touch index.js |
1 | npm i express --save |
添加 .gitignore
文件
1 | .DS_Store |
index.js文件的代码:
1 | const express = require("express"); |
这段代码说明:有一个在端口为 8000 的接口 ‘/getName。 接口返回
舒丽琦`。
现在我们启动这个服务:node index.js
。 打开浏览器访问:http://localhost:3000/api/getName
。可得到:
前端部分
我们使用Vue
的脚手架vue-cli
来构建一个前端的框架:
1 | vue create web |
1 | cd web |
1 | npm install axios --save |
我们把App.vue
代码改成:
1 | <template> </template> |
最后启动一下前端服务npm run dev
。
结果及其原因
这时候我们后端和前端是准备完毕了, 我们来看看结果吧!打开http://localhost:8080/
。然后打开控制台,得到的结果如下:
我们发现是报错了。一看报错信息就知道你产生了跨域的问题。那跨域问题请求到底是什么返回什么呢?
我们继续 debugger
发现reponse:
undefined,提示消息:
Network Error`。 那是不是说明请求没有到后端呢?我们来试试。
我们在后端的接口函数里面打印个log
。然后看请求的时候, 看请求是否到了后端:
1 | const express = require("express"); |
最后我们前端页面请求下,结果是这样的:
那就说明,有跨域的时候, 请求是到达了后端的, 并且后端还返回了 数据。只是在浏览器被拦截了。
跨域产生的原因
经过上面的验证,我们知道跨域是浏览器做了拦截,并且报了错。那为什么浏览器会做拦截呢?
那当然是为了安全问题。比如著名的 CSRF攻击。XSS 攻击。
所以浏览器因为安全问题而引入了同源策略:
只有当 协议,域名,端口 三者都相等时,才不会产生跨域问题。就是说是同源,才能读取服务器的响应。
当前的 url | 请求的 url | 是否跨域 |
---|---|---|
https://shuliqi.github.io | http://shuliqi.github.io | 是,协议不同(https/http) |
https://xiaoxiaoshu.github.io | https://shuliqi.github.io | 是,域名不同(xiaoxiaoshu.github.io/shuliqi.github.io) |
https://shuliqi.github.io:3000 | https://shuliqi.github.io:8080 | 是,端口不同(3000/8080) |
但是 html 中的 img
,script
, link
, iframe
等是允许跨域加载资源的
解决跨域问题
上面例子中,我们由于同源策略的原因(其中域名,端口不相同)产生跨域,导致浏览器拦截并报错。那我们如何解决呢?
前端 proxy
浏览器是禁止跨域的,但是服务器不禁止,所以我们前端可以使用webpack
给我们的本地起一个服务,作为请求的代理对象。
由于我们的项目是vue-cli3
脚手架搭建的。所以webpack
基础配置全部内嵌了,所以我么初始化项目之后Webpack
的config
初始化的配置不见了。但是Vue-cli3
给我们留了一个vue.config.js
文件供们对webpack
进行自定义配置。
我们在我们的vue
项目的根目录中添加vue.config.js
内容如下:
1 | module.exports = { |
上面的每一步解释已经很清楚了。由于我们做道代理的时候重写了请求 url。所以我们代理服务器最终向目标服务请求的链接是:http://localhost:8080/ /getName
。 所以我们后端写的接口也去掉api
。
/serve/index.js:
1 | const express = require("express"); |
最后我们在请求的时候,需要注意将axios
的baseUrl
改成 api
App.vue 文件修改如下:
1 | axios.get('http://localhost:3000/api/getName') ---> axios.get('/api/getName') |
最后前端和后端服务都重启。打开浏览器,结果如下:
我们可以看到,我们再开发环境成功请求到数据。
<<<<<<< HEAD
cors 方式
=======
044ffb97f97af90574daa890024f349cc42d01d3
cors 方式
上面解决我们在开发环境遇到的跨域问题,但是我们打包上线的话, 我们做的配置是不生效的。自然而然也就产生了跨域。那么这种情况就可以使用cors
方式来解决跨域。
cors
称: 跨域资源共享(Cross-origin resource sharing), 是一中 ajax 跨域请求资源的方式。但是这种方式是有兼容性的问题的
- cors 必须 浏览器和服务端同时支持,才能实现跨域
- 这种方式几乎所有的浏览器都支持, 但是 IE 需要 IE10 以上才能支持
- IE8,IE9 需要通过
XDomainRequest
来实现。
我们知道请求会分为简单的请求和复杂的请求:
简单的请求:
请求方式是 GET, POST, HEAD 之一;
Content-Type 的值是
text/plain
,multipart/form-data
,application/x-www-form-urlencoded
之一;
那么这次的请求就是简单的请求。
复杂的请求:
请求方式是下面方式之一:PUT,
1
2
3
4
5
6PUT;
DELETE;
CONNECT;
OPTIONS;
TRACE;
PATCH;Content-Type 的值不属于下列之一:
1
2
3application / x - www - form - urlencoded;
multipart / form - data;
text / plain;
对于简单的请求,对于简单的请求,浏览器会直接发送 cors 请求,具体来说就是在 header 中加入 origin 请求头字段。在响应头回服务器设置相关的 cors 请求,响应头字段为允许跨域请求的源。
而对于复杂的请求,浏览器会先自动发送一个 options
请求浏览器是否支持该请求, 如果不支持,则控制台直接报错, 如果支持, 那么就会发送真正的请求到后端。
我们继续使用我们上面的产生跨域的例子:
serve/index.js:
1 | const express = require("express"); |
然后起我们的前端服务npm run serve
和后端服务node index.js
, 打开我们的http://localhost:8080/
可得到结果如下:
后端代理
第二种方式简单,但是还是会在一定的程度上有风险的,或者某些浏览器不支持的话。那也是没作用的。那第一种方式我们是前端实现代理, 那后端其实也是可以实现代理的。
这里我们以 express
为例子. 首先后端安装 :
1 | npm install http-proxy-middleware --save |
新建 serve.js
1 | const express = require("express"); |
这里我们监听 8000 端口, 当接收到请求前缀是/api
, 我们就代理到 3001 端口。
我们的index.js
改成 api.js
, 内容不变
1 | const express = require("express"); |
这里我们监听的是 3001 端口。这里才是真正的接口响应的部分。
我们新建 index.html. 内容如下:
1 |
|
<<<<<<< HEAD
我们把我们的 html 静态文件放在服务的 8000 端口下面。当请求的时候, 整个过程是这样:
前端页面发起请求 —> 后端的 8000 服务接收请求,并代理到 3001 端口。—-> 3001 端口处理响应
044ffb97f97af90574daa890024f349cc42d01d3