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