起因是我要做一个网站的登录操作模拟,之前尝试的时候都没啥问题,一个 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 的源代码。
request 库在请求时,options 中添加:
followAllRedirects:true
我有尝试过,但似乎仍然有这个问题