title: 记录 Got(Node.js) 代理 HTTP 请求的坑 date: 2021-01-27 19:05:25
在过去,request 模块几乎是 Node.js 端的不二选择,可惜已被放弃维护。如今流行的模块虽然变多,但不意味着它们足够成熟,我还是倾向于专注 Node.js 端的那几个。
需求越简单,选择越不重要。不过相较而言,Got 的接口设计看起来更友好,并且它是做到支持 Connection Timeout 和 Read Timeout 的少数。
见 https://github.com/sindresorhus/got/#comparison 的 Advanced timeouts
实际用下来,还是遇到了坑,顺便扒了一眼 Got 的代码。
<!--more-->先解释一下为什么不用原生 http 模块吧。
毕竟在 Node.js 提供代理服务是非常容易的事儿,普遍的优化无非是:
不用安装第三方依赖,基于原生 http 写上十来行代码即可完工,所以我自己很喜欢直接用 http 做一些工作。
但如果代理需求变得复杂,使用现成的轮子才能利于队友们(也包括自己)维护。
Got 默认对响应执行 decompress,对于代理而言毫无意义,需要关掉。
async _onResponseBase(response: IncomingMessageWithTimings): Promise<void> {
const {options} = this;
if (options.decompress) {
response = decompressResponse(response);
}
}
这在 decompress 的文档上有说明,但是一点儿都不醒目。
If this is disabled, a compressed response is returned as a Buffer. This may be useful if you want to handle decompression yourself or stream the raw compressed data.
最坑的是,我是因为遇到了 stream 的 bug (#issues/1279) 才注意到这个选项。在开启 decompress 的情况下,使响应值的 content-length 是错误的,该 bug 会导致返回的结果不完整。
关闭 decompress 之后,响应开始变慢到肉眼可见得慢。
原因如下:
async _makeRequest(): Promise<void> {
const {options} = this;
const {headers} = options;
if (options.decompress && is.undefined(headers['accept-encoding'])) {
headers['accept-encoding'] = supportsBrotli ? 'gzip, deflate, br' : 'gzip, deflate';
}
}
accept-encoding 只在 decompress 为 true 的时候设置,否则无法享用 gzip 等压缩带来的优化。
const got = require('got');
const HttpAgent = require('agentkeepalive');
const HttpsAgent = HttpAgent.HttpsAgent;
const sec = 1000;
const min = 60 * sec;
const supportsBrotli = process.versions.brotli;
const request = got.extend({
agent: {
http: new HttpAgent(),
https: new HttpsAgent(),
},
// content-length is not set correctly when streaming with decompress
// #issues/1279
decompress: false,
// accept-encoding won't be set without decompress
headers: {
'accept-encoding': supportsBrotli ? 'gzip, deflate, br' : 'gzip, deflate',
},
timeout: {
connect: 3 * sec,
socket: 2 * min,
},
});
这大概率不是最终版,立一个 flag,如果再遇到问题,就自己从头写一个专用于代理场景的 http 库。