起因是我要做一个网站的登录操作模拟,之前尝试的时候都没啥问题,一个 POST 请求就全部搞定了,也没啥花里胡哨的东西,请求载荷里带上账号密码和时间戳,稳稳当当就能拿到登录 cookie,可惜好景不长,前几天的时候发现这个接口出了点小变化,即直接使用 POST 给接口发请求,收到的 Response 的状态码为 307,这可把我难住了,因为 3XX 的状态码理论上都是库、浏览器自动处理的,不应该作为错误抛出,除非重定向的次数太多,超出上限。

因此为了抹平这个接口的变化对整个程序的影响,我做了诸多尝试:

懒人的办法:使用 Puppeteer 库模拟人工操作

好处是在网络条件良好的情况下成功率比较高,且完全模拟人类的操作,不需要考虑访问的接口变化。

坏处是在大量账号登录的场景下稳定性不足,经常会出现网页加载不出来的状况,且速度较慢,网页的迭代速度由于营销活动的变化会比接口迭代的速度要快,因此后续很可能会有新的问题出现。

换网络请求库:从 Request 到 Axios

nodejs 上流行的网络请求库都试了一遍,我的程序原本用的网络请求库是 SuperAgent,但是它在遇到 307 重定向的时候会直接抛出错误,然后我尝试了 Request,Axios 还有 node-fetch,最后的结论是在不添加任何配置的情况下,以上几个网络请求库都处理不了这个问题,而且直到现在我也不知道如何配置。

柳暗花明又一村:Postman

说来简单,我为了对比 307 和 307 之后第二个 200 请求的差别,我把两个请求导入 Postman,再一个一个发送,神奇的事情发生了,原本 307 的请求,在 Postman 中竟然直接成功了??我查了半天,最后在 Postman 的设置页面找到了一个选项:

当我打开 follow redirects 的时候,网络请求就是正常的,如果我关掉这个选项,那么 Postman 就会抛出同样的 307 错误。

那么有没有办法让 nodejs 程序通过 Postman 发送请求呢?

这种行为虽然看上去有点脱裤子放屁,但是,结果论而言,只要实现了比什么都强,所以我打算找一找有没有 cli 版本的 Postman,没想到,Postman 官方竟然真的有一个 npm 库 newman,这个库的主要功能是进行网络测试,即传入一组网络请求的配置(collection),然后得到一个测试结果,其中的网络请求的效果与 Postman 中直接用 gui 执行是一样。

因此,我就在想,能不能用这个变通的工具,来实现 Postman 代理网络请求?

因此我就将之前导入到 Postman 的请求以 newman 需要的格式导出成文件,然后把里面的关键信息替换成每次请求的信息,这样 newman 以为自己在做测试,其实是在帮我发送网络请求。

那么 newman 要怎样获取每次执行之后的结果呢?

return new Promise((resolve, reject) => {
    newman
        .run({
            collection: collection,
            reporters: "progress",
        })
        .on("start", function (err, args) {
            // on start of run, log to console
            console.log("running a collection...");
        })
        .on("done", function (err, summary) {
            if (err) {
                reject(err);
            } else {
                const r = summary.run.executions[0].response.json();
                resolve(r);
            }
        });
});

如上所示,在事件 "done" 中可以获取 summary,summary 中有 response 对象,调用其 json 方法,就可以获得最终结果!

总结

我还是懒得去找为什么另外几个库失败了,而 newman 可以成功,我怀疑跟其中处理重定向的逻辑有关,而 newman 的代码是开源的,有兴趣的朋友可以去看看 newman 的源代码。