6

记录 Got(Node.js) 代理 HTTP 请求的坑

 3 years ago
source link: http://claude-ray.com/2021/01/27/node-http-stream-proxy/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

记录 Got(Node.js) 代理 HTTP 请求的坑

Posted on

2021-01-27

Disqus: 0 Comments
Symbols count in article: 682 Reading time ≈ 2 mins.

在过去,request 模块几乎是 Node.js 端的不二选择,可惜已被放弃维护。如今流行的模块虽然变多,但不意味着它们足够成熟,我还是倾向于专注 Node.js 端的那几个。

需求越简单,选择越不重要。不过相较而言,Got 的接口设计看起来更友好,并且它是做到支持 Connection Timeout 和 Read Timeout 的少数。

https://github.com/sindresorhus/got/#comparison 的 Advanced timeouts

实际用下来,还是遇到了坑,顺便扒了一眼 Got 的代码。

native http?

先解释一下为什么不用原生 http 模块吧。

毕竟在 Node.js 提供代理服务是非常容易的事儿,普遍的优化无非是:

  • 使用 http.Agent。用它来支持 keepAlive,关闭 Nagle 算法等等。
  • 使用 stream.pipe()。相对于“接收-等待-发送”的模式,一方面主要是节省内存,另一方面可以减少等待,提高传输效率(但也不是绝对的)。

不用安装第三方依赖,基于原生 http 写上十来行代码即可完工,所以我自己很喜欢直接用 http 做一些工作。

但如果代理需求变得复杂,使用现成的轮子才能利于队友们(也包括自己)维护。

decompress

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 会导致返回的结果不完整。

accept-encoding

关闭 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 等压缩带来的优化。

solution

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 库。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK