從 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})
})