在一个项目中,我们要实现一个类似于apifox的接口测试功能,我们项目使用的是React的纯前端框架,需求是对各种域的api进行请求测试,想过前端代理,但是代理的配置在 webpack的配置文件里面,不能够灵活的修改代理。
最终解决方案:使用express + axios 搭建一个代理服务器,做中间层转发,可以对任意跨域请求做代理转发
实现思路:
就是前端---请求---> nodejs ----请求---->后端 ----响应--->nodejs--数据处理---响应---->前端。
什么是中间层
nodejs是起中间层的作用,即根据客户端不同请求来做相应的处理或渲染页面,处理时可以是把获取的数据做简单的处理交由底层java那边做真正的数据持久化或数据更新,也可以是从底层获取数据做简单的处理返回给客户端。
如何实现:
第一步先初始化一个node服务器,并且声明一个接收所有请求的接口
const express = require("express");
const ApitiRef = require("./router/ApitoRef");
const app = express();
const PORT = 3000;
app.use(express.urlencoded({ extended: true }));//解析body参数
app.use(express.json());//解析参数
app.all("*", ApitiRef);
app.listen(PORT, () => {
console.log(`http://localhost:${PORT}`);
});
- 配置解析参数的中间件
第二步,接收客户端的请求数据
- 处理get,还有不带有formData的post,delete,options
const axios = require("axios");
const ApitiRef = async (req, res) => {
const { method, headers, body } = req;
console.log("🚀 ~ file: ApitoRef.js:5 ~ apitoRef ~ body:", body);
//指定需要跨域访问的 url
const url = req.query.baseurl + req.originalUrl;
console.log("🚀 ~ file: ApitoRef.js:7 ~ apitoRef ~ url:", url);
// 发送请求到目标服务器
try {
const response = await axios({
method,
url,
headers,
data: body,
});
console.log(response);
// 将目标服务器的响应发送给客户端
res.status(response.status).send(response.data);
} catch (error) {
console.log(error.message);
// 处理错误并发送给客户端
res.status(error.response.status || 500).send(error.message);
}
};
module.exports = ApitiRef;
- 这一步中获取了query中的baseurl参数,这个是用来指定需要请求的接口地址前缀,以此来实现对于任何域都能访问的功能
- 请求实例,测试了一个前缀为 http://47.116.41.66:9527 的接口,除了接口必须的四个参数之外,还需要传递一个baseurl,指定跨域地址
第三步实现对带有formData数据的转发,
这一块有点坑,废了我好长时间
const axios = require("axios");
const FormData = require("form-data");
const multer = require("multer");
const fs = require("fs");
const apitoRef = async (req, res) => {
const { method, headers, body } = req;
const url = req.query.baseurl + req.originalUrl;
console.log("🚀 ~ file: ApitoFormData.js:9 ~ apitoRef ~ url:", url);
try {
let response;
// 如果是 multipart/form-data 类型,使用 multer 处理文件上传
multer({
limits: {
files: 10, // 最多上传 10 个文件
fileSize: 512000 * 8, // 单个文件最大 4MB
},
dest: "attachment/", // 文件暂存上传目录
}).any()(req, res, async (err) => {
if (err) {
console.log(
"🚀 ~ file: ApitoRef.js:24 ~ apitoRef ~ 文件上传错误:" + err
);
return res.status(400).send("文件上传错误:" + err);
} else {
// 如果上传成功,将文件数据放入 FormData 对象
const formData = new FormData();
for (const file of req.files) {
//文件读取暂存库中的数据
const fileStream = fs.createReadStream(file.path);
formData.append(file.fieldname, fileStream, {
filename: file.originalname,
});
}
response = await axios({
method,
url,
headers: {
...headers,
"Content-Type": "multipart/form-data",
},
data: formData,
});
// 将目标服务器的响应发送给客户端
res.status(response.status).send(response.data);
}
});
} catch (error) {
console.error("请求错误:", error);
// 处理不同类型的错误,并向客户端提供有意义的错误消息
if (error.response) {
// 如果目标服务器返回错误响应,将错误消息传递给客户端
res.status(error.response.status).send(error.response.data);
} else if (error.request) {
// 如果请求没有得到响应,返回自定义的错误消息
res.status(500).send("无法连接到目标服务器");
} else {
// 其他类型的错误,返回自定义的错误消息
res.status(500).send("发生了未知错误");
}
}
};
module.exports = apitoRef;
测试一下:
- 先传入必须的baseurl,还有我使用的上传接口所必须的id
- 上传文件,显示上传成功。说明已经转发成功!
合并上面两种类型的转发--> 最终版代码,支持全类型转发😎(应该是)
const axios = require("axios");
const FormData = require("form-data");
const multer = require("multer");
const fs = require("fs");
const apitoRef = async (req, res) => {
const { method, headers, body } = req;
const url = req.query.baseurl + req.originalUrl;
try {
let response;
if (
headers["content-type"] &&
headers["content-type"].includes("multipart/form-data")
) {
// 如果是 multipart/form-data 类型,使用 multer 处理文件上传
multer({
limits: {
files: 10, // 最多上传 10 个文件
fileSize: 512000 * 8, // 单个文件最大 4MB
},
dest: "attachment/", // 文件暂存上传目录
//any,接收任意参数的文件
}).any()(req, res, async (err) => {
if (err) {
console.log(
"🚀 ~ file: ApitoRef.js:24 ~ apitoRef ~ 文件上传错误:" + err
);
return res.status(400).send("文件上传错误:" + err);
} else {
// 如果上传成功,将文件数据放入 FormData 对象
const formData = new FormData();
for (const file of req.files) {
//文件读取暂存库中的数据
const fileStream = fs.createReadStream(file.path);
formData.append(file.fieldname, fileStream, {
filename: file.originalname,
});
}
response = await axios({
method,
url,
headers: {
...headers,
"Content-Type": "multipart/form-data",
},
data: formData,
});
// 将目标服务器的响应发送给客户端
res.status(response.status).send(response.data);
}
});
} else {
// 如果不是 multipart/form-data 类型,直接转发请求体数据
response = await axios({
method,
url,
headers,
data: body,
});
// 将目标服务器的响应发送给客户端
res.status(response.status).send(response.data);
}
} catch (error) {
console.error("请求错误:", error);
// 处理不同类型的错误,并向客户端提供有意义的错误消息
if (error.response) {
// 如果目标服务器返回错误响应,将错误消息传递给客户端
res.status(error.response.status).send(error.response.data);
} else if (error.request) {
// 如果请求没有得到响应,返回自定义的错误消息
res.status(500).send("无法连接到目标服务器");
} else {
// 其他类型的错误,返回自定义的错误消息
res.status(500).send("发生了未知错误");
}
}
};
module.exports = apitoRef;
- 这里文件的转发,先是存储在本地的attachment文件夹中,然后再加入formdata中,进行转发