使用 nodejs 从 0 实现简单易用的代理功能之 config.proxy
从 0 实现 config.proxy
config.proxy 类似于 webpack 的 devServe 中的代理, 但更直观易用.
本文为 mockm 的实现过程, 编写此系列文章 1 是为了抛砖引玉, 让想实现类似工具的朋友可以一起学习. 2 是也给自己做一个简单梳理.
类型: 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})
})