使用 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})
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章