express 做中间层转发任意跨域请求

219 阅读5分钟

在一个项目中,我们要实现一个类似于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}`);
});

  • 配置解析参数的中间件

第二步,接收客户端的请求数据

  1. 处理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,指定跨域地址

image.png

第三步实现对带有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

image.png

  • 上传文件,显示上传成功。说明已经转发成功!

image.png

合并上面两种类型的转发--> 最终版代码,支持全类型转发😎(应该是)

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中,进行转发

欢迎大佬前来指正