从一个 webpack dev server 的 proxy middleware 聊解决问题的方式

1,823 阅读4分钟

背景

我最近在参与一个协作项目的开发,这个项目可以多人协作,每次启动项目的时候,都需要到线上去复制一个已经登录用户的 token,然后设置到本地,才能正常开发。本地 mock 数据是不大可能的,因为需要测试多个用户之间协作,所以需要连接 server,就导致每次 token 过期就很麻烦,复制 token -> 重启 server(但是进度比较赶,可能大家觉得可以忍受,然后我忍不了,就着手解决了)。

解决思路

在 proxy 中增加获取 token 的步骤,获取到 token 后设置到 request header 中。 由于 token 获取需要登录 cookie(介绍一下,用户有统一的登录认证,但是本项目的 token 是独立的,需要在用户登录后才能获取),所以我还需要先登录,到这大概思路有了,下面用一个图介绍一下。

image.png

所以到这就有关键的一步,怎么完成获取 获取 cookie 以及获取 token 这一步。

webpack-dev-server

我最开始是关注到 webpack-dev-serverbeforeafter 两个配置项(现在新版的应该是OnBeforeSetupMiddlewareOnAfterSetupMiddleware),我可以在这去拦截请求,然后获取 token,这两个配置项都是 webpack-dev-server 暴露出来的 hooks,在这两个配置项里面,我们可以拿到当前 webpack-dev-server 的实例,前面都是我看文档得到的,然后因为我想进一步了解,所以我就去了他的仓库里面看了下具体代码,这里贴一下链接,然后看到了下面的代码。

setupOnAfterSetupMiddlewareFeature() {
    this.options.onAfterSetupMiddleware(this);
}

看到 this 之后,我们在文档里面看到

image.png

这个用法,是不是很像 nodeexpress,然后我们继续找,到这 我们可以看到这个

image.png

setupApp 到此我们可以明确这个 devServer.app 就是一个 express 实例,所以在 OnBeforeSetupMiddlewareOnAfterSetupMiddleware 阶段我们可以注册自己的插件。 然后就有了第一版的 tokenMiddleware,大概代码长下面那样

class TokenUtil {
    constructor() {
        this.token = ''
    }
    async refresh() {
        // request token
        this.token = 'xxxx'
    }
}
createRefreshToken() {
    const tokenUtil = new TokenUtil()
    const LoginUtil = new LoginUtil()
    return async (req, res, next) => {
        // await login
        // await token
        // setHeader
    }
}
class LoginUtil {
    constructor() {
        this.logined = false
    }
    async refresh() {
        // request cookie
        // cache cookie
    }
}

webpack-dev-server 配置

module.exports = {
    after: (ds) => {
        ds.app.use(createRefreshToken())
    }
}

然后经过调试之后发现我需要做很多额外的工作,判断是否是需要带 token 的请求之类的,做了很多额外操作,因为这些都只需要在 proxy 中做一下配置就好了,于是我就去翻了 proxy 文档。

webpack-dev-server proxy

然后对比了一下,发现有几个配置项是合适的,routeronProxyResonProxyReq,经过尝试发现只有 router 符合要求,因为在 request 进来之后,我们需要做 token 获取的请求,在这我们需要把他 await 下来,onProxyRes 以及 onProxyReq 只是监听了对应的事件,并不能将请求 hold 下来,所以我们需要在 router 里面做完获取 cookie 以及获取 token。很快我就把代码改完了,很快就发现 bug 住了,有一个库解析 protocol 的时候获取到的 protocolundefined,然后根据错误栈我找到了 http-proxy-middleware 下图为 http-proxy-middleware 仓库代码。 image.png

在这会拿到当前的 proxy 配置项,然后我们看上面的 prepareProxyRequest 方法,然后我就在这打印,发现拿到的 target 配置项是一个 pending 的 Promise,到这我们就发现情况不太对,明明文档上写的是可以传入异步函数,不死心,就进到 prepareProxyRequest 方法里面发现下面的结果

image.png

image.png

到这我们可以看到这确实是可以 await 的,难不成版本有问题?然后我找到了这个 issue

image.png

呀,果然用户有需求才有仓库的完善,然后我知道了这个异步的 router 到 2.x 版本才有。于是我去看了我们现在使用的版本

react-scripts

我们项目用的是 create-react-app 初始化的,没有 eject 所以很多东西都被隐藏了,于是我到 package.lcok.json 里面找到了当前使用的 http-proxy-middleware 版本,发现是 0.19.xx 的,现在问题就来了,升级很难,整个改动都很大。但是不升级,这个目前又解决不了,于是我去看了 react-scripts,进去之后发现这个目录跟 eject 出来的目录很像

image.png

于是我就到 config 目录里面找到了我要的东西

image.png

我可以自定义 proxy middleware(在这比较顺利是因为之前我有过 eject react 脚手架的经验,所以找的会比较顺利),于是就有了下面的一个版本的 token middleware

module.exports = function (app) {
    app.use('/prefix', createRefreshToken())
}

个人经验

平时多看,多 debug,我之前将 create-react-app eject 出来就是为了去 debug 这个脚手架的构造,以及学习他的结构。

遇到问题,要敢于深入到源码里面,将流程从你理解的代码层面来理解清楚,有时候文档可能不会写的很清楚,都是开源产品,有的作者可能精力不会有那么多来完善文档,这个时候就需要你自己去看作者代码,理解他,像庖丁解牛一样。