likes
comments
collection
share

使用 nodejs 从 0 实现 Restful API 和代理覆盖

作者站长头像
站长
· 阅读数 24

使用 nodejs 从 0 实现 Restful API 和代理覆盖

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

关于如何实现代理, 请异步上一篇文章使用 nodejs 从 0 实现简单易用的代理功能.

现在我们使用 express 创建一个同时支持 WebSocket 和 http 的 api:

基础版

const jsonServer = require(`@wll8/json-server`)
const expressWs = require(`@wll8/express-ws`)
const { Proxy } = require(`./obj.js`)

const {app} = expressWs(jsonServer.create())

const config = {
  proxy: {
    '/': `http://httpbin.org/`,
    '/icon': `http://baidu.com/favicon.ico`
  },
}

app.ws(`/abc`, (ws, req) => {
  ws.send(`abc`)
  ws.on(`message`, (data) => {
    ws.send(String(data))
  })
})
app.get(`/abc`, (req, res) => {
  res.json(`abc`)
})

new Proxy({
  app,
  proxy: config.proxy,
})

app.listen(3000)

原始的 express 创建 WebSocket 接口时是没有 app.ws 这样的做法的, 当我们使用 @wll8/express-ws 包装过后, 就可以使用 app.ws 这种写法简单创建 ws api 了.

题外话:

@wll8/express-ws 是发现 express-ws 的一些不足之后创建的, 可以访问 github.com/wll8/expres… 查看它们的差异, 可以访问 juejin.cn/post/709935… 这篇文章查看实现过程.

上面的代码演示了如何配合 proxy 代理一起使用, 主要的应用场景为:

假设前端代理了一个后端的接口, 但是这个接口暂时不可用. 那么我们可以创建一个与后端接口地址相同的接口, 例如 /abc, 这样当我们请求 http://127.0.0.1:3000/abc 时就会访问我们自己的接口, 而不是后端的接口, 这就实现了接口模拟功能.

需要注意的是, 上面的 app.xxx 需要放置于 new Proxy 的前面, 这样才能实现优先进入自定义 api, 而不是 proxy.

升级版

上面的各自接口还需要 app.xxx 还挺麻烦的, 下面我们来优化一下, 实现 config.api 来更快捷的创建自己的 api.

例如, 如果是 use 时, 就作为 express 中间件使用, 如果值是基本数据类型时, 就直接作为数据返回:

if([
  `string`,
  `number`,
  `object`,
  `array`,
  `boolean`,
  `null`,
].includes(tool.type.isType(val))) { // 如果配置的值是 json 支持的数据类型, 则直接作为返回值, 注意, 读取一个文件也是对象
  const backVal = val
  if(method === `ws`) {
    val = (ws, req) => {
      const strData = JSON.stringify(backVal)
      ws.send(strData)
      ws.on(`message`, (msg) => ws.send(strData))
    }
  } else {
    val = (req, res, next) => res.json(backVal)
  }
}

如果有多个相同的 api 路径都需要执行一样的函数时, 可以通过别名配置:

api = Object.entries(api).reduce((acc, [key, val]) => {
  if(val instanceof Side) {
    const sideObj = {
      alias: [], // 路由别名
    }
    val = {
      ...sideObj,
      ...val,
    }
    val.alias.forEach(alias => {
      acc[alias] = val.action
    })
    side[key] = val
    val = val.action
  }
  acc[key] = val
  return acc
}, {})

这是使用别名时的示例:

config = ({side}) => ({
  api: {
    '/pets': side({
      alias: [`/pets2`, `put /pets3`],
      action: 123,
    }),
  },
})

上面使用 side 函数, 让 * /pets* /pets2 以及 put /pets3 这几个接口都返回 123.

与以下代码等效:

config = ({side}) => ({
  api: {
    '/pets2': 123,
    'put /pets3': 123,
    '/pets': 123,
  },
})

结果

类型: object | function 默认: {}

自建 api.

  • object 对象的 key 为 api 路由.
  • function 可以获得工具库, 参考 config.api.fn. 函数应返回一个对象.

当与 config.proxy 中的路由冲突时, config.api 优先.

对象的 key 为 api 路由, 请求方法 /路径, 请求方法可省略, 示例:

  • /api/1 省略请求方法, 可以使用所有 http 方法访问接口, 例如 get post put patch delete head options trace.
  • get /api/2 指定请求方法, 例如只能使用 get 方法访问接口
  • ws /api/3 创建一个 websocket 接口
  • use /api/4 自定义一个中间件, 作用于任何 method 的任何子路由

非 use 时, value 可以是函数或 json, 为 json 时直接返回 json 数据.

api: {
  // 当为基本数据类型时, 直接返回数据
  'get /api/1': {msg: `ok`},
  // http 接收的参数, 参考 example 中间件 http://expressjs.com/en/guide/using-middleware.html
  'get /api/2' (req, res, next) {
    res.send({msg: `ok`})
  },
  // websocket 接收的参数, 参考 https://github.com/websockets/ws
  'ws /api/3' (ws, req) {
    ws.on('message', (msg) => ws.send(msg))
  }
  // 使用中间件实现静态资源访问, config.static 就是基于此方式实现的
  'use /news/': require('serve-static')(`${__dirname}/public`),
  // 拦截 config.db 生成的接口
  '/books/:id' (req, res, next) { // 在所有自定义 api 之前添加中间件
    req.body.a = 1 // 修改用户传入的数据
    next()
    res.mm.resHandleJsonApi = (arg) => {
      arg.res.locals.data // 当前接口的 json-server 原始数据
      arg.data // 经预处理的数据, 例如将分页统计放置于响应体中
      arg.resHandleJsonApi // 是全局 config.resHandleJsonApi 的引用, 若无需处理则直接 return arg.data
      arg.data.a = 2 // 修改响应, 不会存储到 db.json
      return arg.resHandleJsonApi(arg)
    }
  },
},
转载自:https://juejin.cn/post/7231053735308214333
评论
请登录