http.Agent

Agent类用于管理HTTP客户端连接的持久性与复用性。它维护一个给定主机和端口的待处理请求队列,为每个请求重用单个套接字(socket)连接。

node中发送请求一般都是使用http.get或者http.request,每次请求都是‘建立连接-数据传输-销毁连接’的过程。而内部是通过创建sokcet对象(tcp套接字)来实现通信的,频繁创建销毁肯定是会带来一定的性能损耗的,所以就有了Agent类,它可以对相同的主机和端口的请求复用同一个socket对象达到keepAlive的效果。基本使用方式如下:

const http = require('http');
const keepAliveAgent = new http.Agent({
	// 保留套接字,这样后面的请求就可以复用无需重新创建,默认false
	keepAlive: true,
	// 每个主机允许的最大套接字数量,默认Infinity
	maxSockets: 6,
	// 套接字超时,在该时间范围内复用同一个套接字,默认256
	timeout: 256
});

http.get({
  hostname: 'localhost',
  port: 80,
  path: '/',
  agent: keepAliveAgent  // 仅为这个请求创建新代理
}, (res) => {
  // 使用响应做些事情
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

agent选项默认指向的是http.globalAgent,也就是配置全部都为默认值的agent实例。如果设为false则不使用agent

值得注意的是这里的keepAlivehttp头部的connection: keep-alive不是一个概念。开启keepAlive同时也会将connection设为keep-alive,将agent设为falseconnection也会默认设为close。但这些默认行为并不会与手动设置connection头部冲突,通过下面的例子可以证实:

// server.js
const http = require('http');

const server = http.createServer((req, res) => {
    console.log('connection:' + req.headers['connection']);
    req.on('end', () => {
        console.log('request end');
    });
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
        data: 'Hello World!'
    }));
});

server.listen(3000);
server.on('error', e => {
    console.log(e);
});
server.on('connection', socket => {
    console.log(`create new socket:${socket.remotePort}`);
});

// client.js
const keepAliveAgent = new http.Agent({
    keepAlive: true,
    maxSockets: 3
});
const options = {
    host: '127.0.0.1',
    port: 3000,
    path: '/pathname',
    agent: keepAliveAgent
};

const request = () => {
    return new Promise((resolve, reject) => {
        http.get(options, (res) => {
            res.setEncoding('utf8');
            let rawData = '';
            res.on('data', (chunk) => { rawData += chunk; });
            res.on('end', () => {
                try {
                    const parsedData = JSON.parse(rawData);
                    resolve(parsedData);
                } catch (e) {
                    console.error(e.message);
                    reject(e.message);
                }
            });
            }).on('error', (e) => {
                reject(e.message);
            });
    });
};

(async () => {
    for (let i = 0; i < 3; i++) {
      await request();
    }
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

server.js创建了一个简单的http服务器,而客户端设置了keepAliveagent,并串行发送三个相同请求,结果如下:

create new socket:50328
connection:keep-alive
request end
connection:keep-alive
request end
connection:keep-alive
request end
1
2
3
4
5
6
7

可以看到三个请求只创建了以socket,同时头部的connection也是keep-alive。那么我们将keepAlive设为false再看:

create new socket:51634
connection:keep-alive
request end
create new socket:51635
connection:keep-alive
request end
create new socket:51636
connection:keep-alive
request end
1
2
3
4
5
6
7
8
9

此时socket没有复用,每次都创建了新的实例,但connection依然是keep-alive。那么我们再将agent设为false

create new socket:52724
connection:close
request end
create new socket:52725
connection:close
request end
create new socket:52726
connection:close
request end
1
2
3
4
5
6
7
8
9

这次connection也变为了close

通过这么几个例子也证实了Agenthttp头的keep-alive并无太多关联。正常的网页的http请求必须建立tcp连接,而浏览器会帮我们实现keep-alive复用tcp连接。但是在服务端,我们就得自己实现,而node中的socket就是tcp套接字,通过socket来建立连接。

大家可能注意到上面的多个请求采用的是串行方式,为什么要用串行呢?如果直接用并行请求会怎样?话不多说,直接动手去除await,查看打印结果:

create new socket:56120
create new socket:56121
create new socket:56122
connection:close
request end
connection:close
request end
connection:close
request end
1
2
3
4
5
6
7
8
9

直接就创建了三个socket实例,这是因为http模块使用的是http1.1版本,而1.1版本虽然实现了多路复用,但是一个tcp连接同时只能处理一个请求,无法处理并行请求。而针对并行请求只能创建多个tcp连接,而在node中就是同时创建多个socket实例。

所以简单概括来说,Agent就是nodeconnection:keep-alive的具体实现。