1 | # agentkeepalive
|
---|
2 |
|
---|
3 | [![NPM version][npm-image]][npm-url]
|
---|
4 | [![Known Vulnerabilities][snyk-image]][snyk-url]
|
---|
5 | [![npm download][download-image]][download-url]
|
---|
6 |
|
---|
7 | [npm-image]: https://img.shields.io/npm/v/agentkeepalive.svg?style=flat
|
---|
8 | [npm-url]: https://npmjs.org/package/agentkeepalive
|
---|
9 | [snyk-image]: https://snyk.io/test/npm/agentkeepalive/badge.svg?style=flat-square
|
---|
10 | [snyk-url]: https://snyk.io/test/npm/agentkeepalive
|
---|
11 | [download-image]: https://img.shields.io/npm/dm/agentkeepalive.svg?style=flat-square
|
---|
12 | [download-url]: https://npmjs.org/package/agentkeepalive
|
---|
13 |
|
---|
14 | The enhancement features `keep alive` `http.Agent`. Support `http` and `https`.
|
---|
15 |
|
---|
16 | ## What's different from original `http.Agent`?
|
---|
17 |
|
---|
18 | - `keepAlive=true` by default
|
---|
19 | - Disable Nagle's algorithm: `socket.setNoDelay(true)`
|
---|
20 | - Add free socket timeout: avoid long time inactivity socket leak in the free-sockets queue.
|
---|
21 | - Add active socket timeout: avoid long time inactivity socket leak in the active-sockets queue.
|
---|
22 | - TTL for active socket.
|
---|
23 |
|
---|
24 | ## Node.js version required
|
---|
25 |
|
---|
26 | Support Node.js >= `8.0.0`
|
---|
27 |
|
---|
28 | ## Install
|
---|
29 |
|
---|
30 | ```bash
|
---|
31 | $ npm install agentkeepalive --save
|
---|
32 | ```
|
---|
33 |
|
---|
34 | ## new Agent([options])
|
---|
35 |
|
---|
36 | * `options` {Object} Set of configurable options to set on the agent.
|
---|
37 | Can have the following fields:
|
---|
38 | * `keepAlive` {Boolean} Keep sockets around in a pool to be used by
|
---|
39 | other requests in the future. Default = `true`.
|
---|
40 | * `keepAliveMsecs` {Number} When using the keepAlive option, specifies the initial delay
|
---|
41 | for TCP Keep-Alive packets. Ignored when the keepAlive option is false or undefined. Defaults to 1000.
|
---|
42 | Default = `1000`. Only relevant if `keepAlive` is set to `true`.
|
---|
43 | * `freeSocketTimeout`: {Number} Sets the free socket to timeout
|
---|
44 | after `freeSocketTimeout` milliseconds of inactivity on the free socket.
|
---|
45 | Default is `15000`.
|
---|
46 | Only relevant if `keepAlive` is set to `true`.
|
---|
47 | * `timeout`: {Number} Sets the working socket to timeout
|
---|
48 | after `timeout` milliseconds of inactivity on the working socket.
|
---|
49 | Default is `freeSocketTimeout * 2`.
|
---|
50 | * `maxSockets` {Number} Maximum number of sockets to allow per
|
---|
51 | host. Default = `Infinity`.
|
---|
52 | * `maxFreeSockets` {Number} Maximum number of sockets (per host) to leave open
|
---|
53 | in a free state. Only relevant if `keepAlive` is set to `true`.
|
---|
54 | Default = `256`.
|
---|
55 | * `socketActiveTTL` {Number} Sets the socket active time to live, even if it's in use.
|
---|
56 | If not set, the behaviour keeps the same (the socket will be released only when free)
|
---|
57 | Default = `null`.
|
---|
58 |
|
---|
59 | ## Usage
|
---|
60 |
|
---|
61 | ```js
|
---|
62 | const http = require('http');
|
---|
63 | const Agent = require('agentkeepalive');
|
---|
64 |
|
---|
65 | const keepaliveAgent = new Agent({
|
---|
66 | maxSockets: 100,
|
---|
67 | maxFreeSockets: 10,
|
---|
68 | timeout: 60000, // active socket keepalive for 60 seconds
|
---|
69 | freeSocketTimeout: 30000, // free socket keepalive for 30 seconds
|
---|
70 | });
|
---|
71 |
|
---|
72 | const options = {
|
---|
73 | host: 'cnodejs.org',
|
---|
74 | port: 80,
|
---|
75 | path: '/',
|
---|
76 | method: 'GET',
|
---|
77 | agent: keepaliveAgent,
|
---|
78 | };
|
---|
79 |
|
---|
80 | const req = http.request(options, res => {
|
---|
81 | console.log('STATUS: ' + res.statusCode);
|
---|
82 | console.log('HEADERS: ' + JSON.stringify(res.headers));
|
---|
83 | res.setEncoding('utf8');
|
---|
84 | res.on('data', function (chunk) {
|
---|
85 | console.log('BODY: ' + chunk);
|
---|
86 | });
|
---|
87 | });
|
---|
88 | req.on('error', e => {
|
---|
89 | console.log('problem with request: ' + e.message);
|
---|
90 | });
|
---|
91 | req.end();
|
---|
92 |
|
---|
93 | setTimeout(() => {
|
---|
94 | if (keepaliveAgent.statusChanged) {
|
---|
95 | console.log('[%s] agent status changed: %j', Date(), keepaliveAgent.getCurrentStatus());
|
---|
96 | }
|
---|
97 | }, 2000);
|
---|
98 |
|
---|
99 | ```
|
---|
100 |
|
---|
101 | ### `getter agent.statusChanged`
|
---|
102 |
|
---|
103 | counters have change or not after last checkpoint.
|
---|
104 |
|
---|
105 | ### `agent.getCurrentStatus()`
|
---|
106 |
|
---|
107 | `agent.getCurrentStatus()` will return a object to show the status of this agent:
|
---|
108 |
|
---|
109 | ```js
|
---|
110 | {
|
---|
111 | createSocketCount: 10,
|
---|
112 | closeSocketCount: 5,
|
---|
113 | timeoutSocketCount: 0,
|
---|
114 | requestCount: 5,
|
---|
115 | freeSockets: { 'localhost:57479:': 3 },
|
---|
116 | sockets: { 'localhost:57479:': 5 },
|
---|
117 | requests: {}
|
---|
118 | }
|
---|
119 | ```
|
---|
120 |
|
---|
121 | ### Support `https`
|
---|
122 |
|
---|
123 | ```js
|
---|
124 | const https = require('https');
|
---|
125 | const HttpsAgent = require('agentkeepalive').HttpsAgent;
|
---|
126 |
|
---|
127 | const keepaliveAgent = new HttpsAgent();
|
---|
128 | // https://www.google.com/search?q=nodejs&sugexp=chrome,mod=12&sourceid=chrome&ie=UTF-8
|
---|
129 | const options = {
|
---|
130 | host: 'www.google.com',
|
---|
131 | port: 443,
|
---|
132 | path: '/search?q=nodejs&sugexp=chrome,mod=12&sourceid=chrome&ie=UTF-8',
|
---|
133 | method: 'GET',
|
---|
134 | agent: keepaliveAgent,
|
---|
135 | };
|
---|
136 |
|
---|
137 | const req = https.request(options, res => {
|
---|
138 | console.log('STATUS: ' + res.statusCode);
|
---|
139 | console.log('HEADERS: ' + JSON.stringify(res.headers));
|
---|
140 | res.setEncoding('utf8');
|
---|
141 | res.on('data', chunk => {
|
---|
142 | console.log('BODY: ' + chunk);
|
---|
143 | });
|
---|
144 | });
|
---|
145 |
|
---|
146 | req.on('error', e => {
|
---|
147 | console.log('problem with request: ' + e.message);
|
---|
148 | });
|
---|
149 | req.end();
|
---|
150 |
|
---|
151 | setTimeout(() => {
|
---|
152 | console.log('agent status: %j', keepaliveAgent.getCurrentStatus());
|
---|
153 | }, 2000);
|
---|
154 | ```
|
---|
155 |
|
---|
156 | ### Support `req.reusedSocket`
|
---|
157 |
|
---|
158 | This agent implements the `req.reusedSocket` to determine whether a request is send through a reused socket.
|
---|
159 |
|
---|
160 | When server closes connection at unfortunate time ([keep-alive race](https://code-examples.net/en/q/28a8069)), the http client will throw a `ECONNRESET` error. Under this circumstance, `req.reusedSocket` is useful when we want to retry the request automatically.
|
---|
161 |
|
---|
162 | ```js
|
---|
163 | const http = require('http');
|
---|
164 | const Agent = require('agentkeepalive');
|
---|
165 | const agent = new Agent();
|
---|
166 |
|
---|
167 | const req = http
|
---|
168 | .get('http://localhost:3000', { agent }, (res) => {
|
---|
169 | // ...
|
---|
170 | })
|
---|
171 | .on('error', (err) => {
|
---|
172 | if (req.reusedSocket && err.code === 'ECONNRESET') {
|
---|
173 | // retry the request or anything else...
|
---|
174 | }
|
---|
175 | })
|
---|
176 | ```
|
---|
177 |
|
---|
178 | This behavior is consistent with Node.js core. But through `agentkeepalive`, you can use this feature in older Node.js version.
|
---|
179 |
|
---|
180 | ## [Benchmark](https://github.com/node-modules/agentkeepalive/tree/master/benchmark)
|
---|
181 |
|
---|
182 | run the benchmark:
|
---|
183 |
|
---|
184 | ```bash
|
---|
185 | cd benchmark
|
---|
186 | sh start.sh
|
---|
187 | ```
|
---|
188 |
|
---|
189 | Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz
|
---|
190 |
|
---|
191 | node@v0.8.9
|
---|
192 |
|
---|
193 | 50 maxSockets, 60 concurrent, 1000 requests per concurrent, 5ms delay
|
---|
194 |
|
---|
195 | Keep alive agent (30 seconds):
|
---|
196 |
|
---|
197 | ```js
|
---|
198 | Transactions: 60000 hits
|
---|
199 | Availability: 100.00 %
|
---|
200 | Elapsed time: 29.70 secs
|
---|
201 | Data transferred: 14.88 MB
|
---|
202 | Response time: 0.03 secs
|
---|
203 | Transaction rate: 2020.20 trans/sec
|
---|
204 | Throughput: 0.50 MB/sec
|
---|
205 | Concurrency: 59.84
|
---|
206 | Successful transactions: 60000
|
---|
207 | Failed transactions: 0
|
---|
208 | Longest transaction: 0.15
|
---|
209 | Shortest transaction: 0.01
|
---|
210 | ```
|
---|
211 |
|
---|
212 | Normal agent:
|
---|
213 |
|
---|
214 | ```js
|
---|
215 | Transactions: 60000 hits
|
---|
216 | Availability: 100.00 %
|
---|
217 | Elapsed time: 46.53 secs
|
---|
218 | Data transferred: 14.88 MB
|
---|
219 | Response time: 0.05 secs
|
---|
220 | Transaction rate: 1289.49 trans/sec
|
---|
221 | Throughput: 0.32 MB/sec
|
---|
222 | Concurrency: 59.81
|
---|
223 | Successful transactions: 60000
|
---|
224 | Failed transactions: 0
|
---|
225 | Longest transaction: 0.45
|
---|
226 | Shortest transaction: 0.00
|
---|
227 | ```
|
---|
228 |
|
---|
229 | Socket created:
|
---|
230 |
|
---|
231 | ```bash
|
---|
232 | [proxy.js:120000] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout
|
---|
233 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0}
|
---|
234 | ----------------------------------------------------------------
|
---|
235 | [proxy.js:120000] normal , 53866 created, 84260 requestFinished, 1.56 req/socket, 0 requests, 0 sockets
|
---|
236 | {" <10ms":75," <15ms":1112," <20ms":10947," <30ms":32130," <40ms":8228," <50ms":3002," <100ms":4274," <150ms":181," <200ms":18," >=200ms+":33}
|
---|
237 | ```
|
---|
238 |
|
---|
239 | ## License
|
---|
240 |
|
---|
241 | [MIT](LICENSE)
|
---|