使用 nodejs 从 0 实现简单易用的代理功能之 config.proxy

从 0 实现 config.proxy

config.proxy 类似于 webpack 的 devServe 中的代理, 但更直观易用.

本文为 mockm 的实现过程, 编写此系列文章 1 是为了抛砖引玉, 让想实现类似工具的朋友可以一起学习. 2 是也给自己做一个简单梳理.

image.png

类型: string | object
默认: http://www.httpbin.org/

代理到远程的目标域名,为对象时每个键是分别对应一个要自定义代理的路由.

注: 是对象时, 需要存在键 / 表示默认域名.
此功能可以自定义拦截过程, 类似 webpack 中的 devServer.proxy .

  • string 直接请求转发到指定地址.
  • object 相当于传入 proxy 的配置.

参考 proxy.

快速修改 json response

支持以简便的方式快速处理 json 格式的 response, 使用数组语法: [A, B].
数组中不同个数和类型有不同的处理方式, 参考下表:

个数[A, B] 类型处理方式处理前操作处理后
0, 1 [any] 直接替换 {a: 1} [][undefined]
      {a: 1} [123] 123
2 [string, any] 替换指路径的值 {a: 1} ['a', 2] {a: 2}
      {a: {b: 1, c: 2}} ['a.b', undefined] {a: {c: 2}}
2 [object, ...] 浅合并 {a: {a: 1}} [{a: {b: 2}, c: 1}, '...'] {a: {b: 2}, c: 1}
  [object, deep] 深合并 {a: {a: 1}} [{a: {b: 2}, c: 1}, 'deep'] {a: {a: 1, b: 2}, c: 1}

A 或 B 支持传入函数, 可以接收 {req, json}, 返回值是前端最终收到的值.

::: details 示例
进一步解释表格中的示例.

直接替换

处理前
{
  "a": 1
}

操作, 直接替换为空
[] 或 [undefined]

处理后
undefined

直接替换

处理前
{
  "a": 1
}

操作, 直接替换为 123
[123]

处理后
123

替换指定路径的值

处理前
{
  "a": 1
}

操作, 把 a 的值替换为 2
['a', 2]

处理后
{
  "a": 2
}

替换指定路径的值

处理前
{
  "a": {
    "b": 1,
    "c": 2
  }
}

操作, 把 a 下面 b 的值删除
['a.b', undefined]

处理后
{
  "a": {
    "c": 2
  }
}

浅合并

处理前
{
  "a": {
    "a": 1
  },
  "c": 1
}

操作, 合并时直接替换, 此示例会直接替换掉 a 对象
[
  {
    "a": {
      "b": 2
    },
    "c": 1
  },
  "..."
]

处理后
{
  "a": {
    "b": 2
  },
  "c": 1
}

深合并

处理前
{
  "a": {
    "a": 1
  }
}

操作, 深层合并对象, 此对象会合并 a 对象
[
  {
    "a": {
      "b": 2
    },
    "c": 1
  },
  "deep"
]

处理后
{
  "a": {
    "a": 1,
    "b": 2
  },
  "c": 1
}

:::

请求转发

可以方便的支持任意路径转发, 以下演示转发到其他域名:

``js {3} proxy: { '/':https://httpbin.org/`,
‘/get’: https://www.httpbin.org/ip,
},



## 实现一个简单代理
在 express 中使用 http-proxy-middleware 实现简单代理

``` js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

app.use('/api', createProxyMiddleware({ target: 'http://www.example.org', changeOrigin: true }));
app.listen(3000);

由于需要使用 json-server 自动实现 restfull-api, 并且由于 json-server 是强依赖 express 的, 所以我们直接使用 json-server 中的 express, 而不是再安装一个 express, 这样可以保持一定的兼容性.

const jsonServer = require(`@wll8/json-server`)
const app = jsonServer.create()

解析 config.proxy 对象

默认情况下, 每个代理都需要自己指定 createProxyMiddleware 的 target 参数和 pathRewrite 参数, 才能实现:

:3000/api => http://baidu.com/api 这样的效果.

实际上对于常用的功能, 至少需要以下配置:

const defaultConfig = {
  ws: true,
  target: origin,
  secure: false,
  changeOrigin: true,
  onProxyReq: (proxyReq, req, res) => {
    // https://github.com/chimurai/http-proxy-middleware/pull/492
    this.httpProxyMiddleware.fixRequestBody(proxyReq, req)
  },
  logLevel: `silent`,
  // proxyTimeout: 60 * 1000,
  // timeout: 60 * 1000,
}

所以为了容易使用, 我们需要做一些转换工作. 例如配置 proxy 的时候:

  • proxy 为字符串时, 直接代理到字符串
  • proxy 为对象时,根据对象 key val 做转换
    • val 为字符串, 代理 key 到 val
    • val 为对象, 则表示自定义 createProxyMiddleware
    • val 为数组, 则表示快捷修改响应体

由于默认情况下基本都只代理到一个根服务, 所以我们提取根服务, 如果代理的目标没有指定其他服务时, 那么我们就使用根服务:

主要是提取 val 中的 origin.

this.rootOriginData = this.parseRootOriginData(this.proxy)

转换整个 proxy 对象为 proxy 配置列表:

prepareProxy (proxy = {}) { // 解析 proxy 参数, proxy: string, object
  const isType = tool.type.isType
  let resProxy = []
  function setIndexOfEnd(proxy) { // 需要排序 key:/ 到最后, 否则它生成的拦截器会被其他 key 覆盖
    const indexVal = proxy[`/`]
    delete proxy[`/`]
    proxy[`/`] = indexVal
    return proxy
  }
  proxy = setIndexOfEnd(proxy)
  resProxy = Object.keys(proxy).map(context => {
    let options = proxy[context]
    const optionsType = isType(options)
    if(optionsType === `string`) { // 转换字符串的 value 为对象
      const rootOptions = proxy[`/`]
      options = {
        pathRewrite: { [`^${context}`]: options }, // 原样代理 /a 到 /a
        target: options.includes(`://`) // 如果要代理的目录地址已有主域
          ? new URL(options).origin // 那么就代理到该主域上
          : { // 否则就代理到 / 设定的域上
            string: rootOptions,
            object: rootOptions.target, // 当主域是对象时则取其 target
          }[isType(rootOptions)],
      }
    }
    if(optionsType === `array`) { // 是数组时, 视为设计 res body 的值, 语法为: [k, v]
      const [item1, item2] = options
      const item1Type = isType(item1)
      const item2Type = isType(item2)
      const deepMergeObject = tool.obj.deepMergeObject

      if((item1Type !== `function`) && (options.length <= 1)) { // 只有0个或一个项, 直接替换 res
        options = {
          onProxyRes (proxyRes, req, res) {
            this.midResJson({proxyRes, res, cb: () => item1})
          },
        }
      }
      if((item1Type === `function`) && (options.length <= 1)) {
        options = {
          onProxyRes (proxyRes, req, res) {
            this.midResJson({proxyRes, res, cb: (json) => item1({req, json})})
          },
        }
      }
      // if ...
    }
    return {
      route: context,
      info: options,
    }
  })
  return resProxy
}

然后依次 use 到 express 的实例上即可:

const proxyList = this.prepareProxy(this.proxy)
proxyList.forEach(item => {
  this.use({route: item.route, config: item.info})
})
posted @ 2023-05-09 09:25  程序媛李李李李蕾  阅读(460)  评论(0编辑  收藏  举报