在 Node.js 中使用 HTTP Agent 实现 keep-alive

不带 Keep-Alive 的请求

我们都知道,浏览器中多个 HTTP 请求是可以通过请求头connection: keep-alive;来复用 TCP 连接的,但是在 Node.js 中怎么实现呢?

比如,下面是一段简单的发起请求的代码:

// server.js const http = require('http') http .createServer((req, res) => { res.writeHead(200) res.end('Hello World') }) .listen(3000) // client.js const http = require('http') function request() { http .request( { host: 'localhost', port: 3000, pathname: '/', }, (res) => { let buffer = [] res.on('data', (chunk) => { buffer.push(chunk) }) res.on('end', () => { console.log(buffer.toString()) }) } ) .end() } request() setTimeout(() => { request() }, 5000)

当我们用 wireshard 抓包分析时,我们发现两次请求的 client 端口是不同的,并且有两个“三次握手”的过程:

使用 http.Agent 发送带有 Keep-Alive 的请求

现在,让我们使用http.Agent来实现keep-alive请求,我们只需要加少量的代码:

// client.js const http = require('http') const agent = new http.Agent({ keepAlive: true, }) function request() { http .request( { host: 'localhost', port: 3000, pathname: '/', agent }, (res) => { let buffer = [] res.on('data', (chunk) => { buffer.push(chunk) }) res.on('end', () => { console.log(buffer.toString()) }) } ) .end() } request() setTimeout(() => { request() }, 5000)

maxSockets表示每个 host 所能建立的最大 TCP 连接数。其默认值是Infinity。如果我们不指定它的值,那每个 HTTP 请求都会建立一个 TCP 连接。

const agent = new http.Agent({ keepAlive: true, maxSockets: 1, })

接下来,我们修改一下代码:

setTimeout(() => { request() }, 10000) // 1000 -> 10000

keep-alive又不生效了!并且我们可以看到服务端 5s 左右后发送了一个 FIN 的包。难道有什么参数可以控制keep-alive的超时时间么?确实,也就是keepAliveTimeout,我们把它设置为 10s:

const http = require('http') const server = http .createServer((req, res) => { res.writeHead(200) res.end('Hello World') }) .listen(3000) server.keepAliveTimeout = 10000

现在keep-alive又可以工作了。

总结

首先 Node.js 的 http 库提供三个主要的函数: http.request, http.get, http.createServer。前两个函数主要是为了创建 http 客户端,向其它服务器发送请求,http.get 只是 http.request 的发送 get 请求的便捷方式;而 http.createServer 是为了创建 Node 服务,比如 Koa 服务框架就是基于 http.createServer 封装的。

而 http.Agent 主要是为 http.request, http.get 提供代理服务的,用于管理 http 连接的创建,销毁及复用工作。http.request, http.get 默认使用 http.globalAgent 作为代理,每次请求都是“建立连接-数据传输-销毁连接”的过程,如果我们想让多个请求复用同一个 connection,则需要重新定义 agent 去覆盖默认的 http.globalAgent,下面我们看一下新建一个agent的需要哪些主要参数:

keepAlive:{Boolean} 是否开启 keepAlive,多个请求公用一个 socket connection,默认是 false。 maxSockets:{Number} 每台主机允许的socket的最大值,默认值为Infinity。 maxFreeSockets:{Number} 处于连接池空闲状态的最大连接数, 仅当开启 keepAlive 才有效。 let http = require('http'); const keepAliveAgent = new http.Agent({ keepAlive: true, maxScokets: 1000, maxFreeSockets: 50 }) http.get({ hostname: 'localhost', port: 80, path: '/', agent: keepAliveAgent }, (res) => { // Do stuff with response });


只有向相同的 host 和 port 发送请求才能公用同一个 keepAlive 的 socket 连接,如果开启 keepAlive ,同一时间内多个请求会被放在队列里等待;如果当前队列为空,该 socket 处于空闲状态,但是不会被销毁,等待下一次请求的到来。

使用 keepAlive 代理,有效的减少了建立/销毁连接的开销,开发者可以对连接池进行动态管理。

大体差不多就是这个意思,详细可以看Node.js 官方文档