vue项目中遇到的跨域问题

最近在做一个全新的Vue前端项目搭建及其开发工作,后端和前端都是分离的,所以避免不了开发环境和生产环境的跨域问题。 开发环境或者是生产环境,前端和后端都是在同一个机器下面部署或者是使用不同的端口号。 当我们的前端资源访问后端服务时得不到数据或没有达到预期的效果。以前也是知道跨域问题的, 但是没有好好总结。那么这篇文章就主要来讲讲遇到的跨域问题。以及如何解决,在 vue 项目中如何解决等。

跨域的问题

上面我们也说了, 现在开发项目大部分都是前后端分离的,那么无论是什么环境就肯定会遇到跨域问题。我们举一个例子来说明:

后端部分

我们使用 express来弄一个后端的服务:

1
mkdir serve
1
cd serve
1
touch index.js
1
npm i express --save

添加 .gitignore文件

1
2
.DS_Store
node_modules

index.js文件的代码:

1
2
3
4
5
6
7
8
9
10
11
const express = require("express");
const app = express();
const port = 3000;

app.get("/api/getName", (req, res) => {
res.send("舒丽琦");
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

这段代码说明:有一个在端口为 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template> </template>
<script>
import axios from "axios";
export default {
name: "App",
created() {
// 去请求我们刚才启动的后端得服务的 getName 接口
axios
.get("http://localhost:8000/getName")
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
},
};
</script>

最后启动一下前端服务npm run dev

结果及其原因

这时候我们后端和前端是准备完毕了, 我们来看看结果吧!打开http://localhost:8080/。然后打开控制台,得到的结果如下:

我们发现是报错了。一看报错信息就知道你产生了跨域的问题。那跨域问题请求到底是什么返回什么呢?

我们继续 debugger

发现reponse:undefined,提示消息:Network Error`。 那是不是说明请求没有到后端呢?我们来试试。

我们在后端的接口函数里面打印个log。然后看请求的时候, 看请求是否到了后端:

1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require("express");
const app = express();
const port = 8000;

app.get("/getName", (req, res) => {
// 加个log
console.log("请求到后端了");
res.send("舒丽琦");
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

最后我们前端页面请求下,结果是这样的:

那就说明,有跨域的时候, 请求是到达了后端的, 并且后端还返回了 数据。只是在浏览器被拦截了。

跨域体现的例子

跨域产生的原因

经过上面的验证,我们知道跨域是浏览器做了拦截,并且报了错。那为什么浏览器会做拦截呢?

那当然是为了安全问题。比如著名的 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基础配置全部内嵌了,所以我么初始化项目之后Webpackconfig初始化的配置不见了。但是Vue-cli3给我们留了一个vue.config.js文件供们对webpack进行自定义配置。

我们在我们的vue项目的根目录中添加vue.config.js内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
// 开发环境
devServer: {
proxy: {
// 代理的标识, 告诉 node, url 前面是 api的就是需要代理的
"/api": {
// 目标地址,一般指后端服务器地址
target: "http://localhost:3000",

// 是否允许跨域
changeOrigin: true,

// 重写实 际Request Url中的'/api'用""代替, 因为我们后端接口没有api
pathRewrite: {
// 我们请求url为:'/api/getNmae'话的,经过http-proxy-middleware的代理服务器时候改成'/getName',然后代理到 target 目标地址
"^/api": "",
},
},
},
},
};

上面的每一步解释已经很清楚了。由于我们做道代理的时候重写了请求 url。所以我们代理服务器最终向目标服务请求的链接是:http://localhost:8080/ /getName。 所以我们后端写的接口也去掉api

/serve/index.js:

1
2
3
4
5
6
7
8
9
10
11
12
const express = require("express");
const app = express();
const port = 3000;
// 去掉'api/getName'中的api,这样前端代理服务器请求过来的才能匹配到
app.get("/getName", (req, res) => {
console.log("请求到后端了");
res.send("舒丽琦");
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

最后我们在请求的时候,需要注意将axiosbaseUrl改成 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
    6
    PUT;
    DELETE;
    CONNECT;
    OPTIONS;
    TRACE;
    PATCH;
  • Content-Type 的值不属于下列之一:

    1
    2
    3
    application / x - www - form - urlencoded;
    multipart / form - data;
    text / plain;

对于简单的请求,对于简单的请求,浏览器会直接发送 cors 请求,具体来说就是在 header 中加入 origin 请求头字段。在响应头回服务器设置相关的 cors 请求,响应头字段为允许跨域请求的源。

而对于复杂的请求,浏览器会先自动发送一个 options请求浏览器是否支持该请求, 如果不支持,则控制台直接报错, 如果支持, 那么就会发送真正的请求到后端。

我们继续使用我们上面的产生跨域的例子:

serve/index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const express = require("express");
const app = express();
const port = 3000;

//设置跨域访问
app.all("*", function(req, res, next) {
// 设置哪个源可以访问我
res.header("Access-Control-Allow-Origin", "*");

// 允许携带哪个头访问我
res.header("Access-Control-Allow-Headers", "X-Requested-With");

// 允许哪个方法访问我
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

next();
});

app.get("/api/getName", (req, res) => {
console.log("请求到后端了");
res.send("舒丽琦");
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

然后起我们的前端服务npm run serve 和后端服务node index.js, 打开我们的http://localhost:8080/

可得到结果如下:

本例子的代码)

后端代理

第二种方式简单,但是还是会在一定的程度上有风险的,或者某些浏览器不支持的话。那也是没作用的。那第一种方式我们是前端实现代理, 那后端其实也是可以实现代理的。

这里我们以 express 为例子. 首先后端安装 :

1
npm install http-proxy-middleware --save

新建 serve.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const express = require("express");
const app = express();
const { createProxyMiddleware } = require("http-proxy-middleware");
const port = 8000;

app.use(
"/api",
createProxyMiddleware({
// 接受到前端的请求,然后转到3001
target: "http://localhost:3001",
changeOrigin: true,
})
);

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

这里我们监听 8000 端口, 当接收到请求前缀是/api, 我们就代理到 3001 端口。

我们的index.js 改成 api.js, 内容不变

1
2
3
4
5
6
7
8
9
10
11
12
const express = require("express");
const app = express();
const port = 3001;

app.get("/api/getName", (req, res) => {
console.log("请求到后端了");
res.send("舒丽琦");
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

这里我们监听的是 3001 端口。这里才是真正的接口响应的部分。

我们新建 index.html. 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<title>首页</title>
<meta charset="utf-8" />
<script
type="text/javascript"
src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"
></script>
</head>
<body>
<script type="text/javascript">
$(function() {
var contextPath = "http://localhost:4000/api/getName";
$.ajax({
type: "get",
data: "click",
url: contextPath,
success: function(data) {
console.log(data);
},
error: function(data) {
console.log(data);
},
});
});
</script>
</body>
</html>

<<<<<<< HEAD

我们把我们的 html 静态文件放在服务的 8000 端口下面。当请求的时候, 整个过程是这样:

前端页面发起请求 —> 后端的 8000 服务接收请求,并代理到 3001 端口。—-> 3001 端口处理响应

044ffb97f97af90574daa890024f349cc42d01d3

文章作者: 舒小琦
文章链接: https://shuliqi.github.io/2021/04/15/vue项目中遇到的跨域问题/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 舒小琦的Blog