├── .eslintignore ├── .eslintrc ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── nodejs.yml ├── .gitignore ├── History.md ├── LICENSE ├── README.md ├── benchmark ├── echo.js ├── proxy.js ├── readme.md ├── siegerc ├── sleep_server.js └── start.sh ├── browser.js ├── example ├── http_agent.js ├── https_agent.js └── server.js ├── index.d.ts ├── index.js ├── lib ├── agent.js ├── constants.js └── https_agent.js ├── package.json └── test ├── fixtures ├── agenttest-cert.pem ├── agenttest-key.pem ├── ca.cnf ├── ca.key ├── ca.pem ├── genkey.sh ├── server.cnf └── ts │ ├── index.ts │ └── tsconfig.json ├── http_agent.test.js ├── https_agent.test.js ├── server_timeout.test.js ├── test-ECONNRESET.test.js ├── test-http-agent-maxsockets-regress-4050.test.js ├── test-https-agent-session-eviction.test.js ├── test-https-ipv6.test.js └── ts.test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | test/fixtures/ts -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ##### Checklist 12 | 13 | 14 | - [ ] `npm test` passes 15 | - [ ] tests and/or benchmarks are included 16 | - [ ] documentation is changed or added 17 | - [ ] commit message follows commit guidelines 18 | 19 | ##### Affected core subsystem(s) 20 | 21 | 22 | 23 | ##### Description of change 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - master 11 | pull_request: 12 | branches: 13 | - main 14 | - master 15 | schedule: 16 | - cron: '0 2 * * *' 17 | 18 | jobs: 19 | build: 20 | runs-on: ${{ matrix.os }} 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | node-version: [8, 10, 12, 14, 16] 26 | os: [ubuntu-latest] 27 | 28 | steps: 29 | - name: Checkout Git Source 30 | uses: actions/checkout@v2 31 | 32 | - name: Use Node.js ${{ matrix.node-version }} 33 | uses: actions/setup-node@v1 34 | with: 35 | node-version: ${{ matrix.node-version }} 36 | 37 | - name: Install Dependencies 38 | run: npm i 39 | 40 | - name: Continuous Integration 41 | run: npm run ci 42 | 43 | - name: Code Coverage 44 | uses: codecov/codecov-action@v1 45 | with: 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | coverage.html 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | 15 | node_modules 16 | npm-debug.log 17 | coverage/ 18 | .nyc_output/ 19 | test/fixtures/ts/*.js 20 | package-lock.json 21 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 4.6.0 / 2024-12-29 3 | ================== 4 | 5 | **features** 6 | * [[`7d79ddd`](http://github.com/node-modules/agentkeepalive/commit/7d79ddd2e4a5685013b4a8fee810a823028d517a)] - feat: export `HttpAgent` as standalone class to avoid namespace clutter and support ESM style imports (#119) (Radoslav Kirilov <>) 7 | 8 | 4.5.0 / 2023-08-06 9 | ================== 10 | 11 | **others** 12 | * [[`1e5e312`](http://github.com/node-modules/agentkeepalive/commit/1e5e312f36491243372dbfee0dd47607e7b3d94a)] - deps: remove debug and depd (#114) (fengmk2 <>) 13 | 14 | 4.4.0 / 2023-08-05 15 | ================== 16 | 17 | **features** 18 | * [[`c7c1e93`](http://github.com/node-modules/agentkeepalive/commit/c7c1e93beba7310d7c2cc9647dd211a686d21cac)] - feat: return socket from createConnection (#113) (Nabeel Bukhari <>) 19 | 20 | 4.3.0 / 2023-03-06 21 | ================== 22 | 23 | **others** 24 | * [[`6f9852b`](http://github.com/node-modules/agentkeepalive/commit/6f9852bf6f674846103e403fd9c84e92fc24f820)] - deps: depd@2.0.0 (#109) (Brian DeHamer <>) 25 | * [[`fd4bd9b`](http://github.com/node-modules/agentkeepalive/commit/fd4bd9b0e0f051de3cb49559d1b0d534a0ded18c)] - test: use npm install (#110) (fengmk2 <>) 26 | * [[`d52822c`](http://github.com/node-modules/agentkeepalive/commit/d52822c1243c689df1c8232a3bb14139cf87fae5)] - chore: update contributors (fengmk2 <>) 27 | 28 | 4.2.1 / 2022-02-21 29 | ================== 30 | 31 | **fixes** 32 | * [[`8b13b5c`](http://github.com/node-modules/agentkeepalive/commit/8b13b5ca797f4779a0a8d393ad8ecb622cd27987)] - fix: explicitly set `| undefined` in type definitions (#99) (Benoit Lemoine <>) 33 | 34 | 4.2.0 / 2021-12-31 35 | ================== 36 | 37 | **fixes** 38 | * [[`f418c67`](http://github.com/node-modules/agentkeepalive/commit/f418c67a63c061c7261592d4553bc455e0b0d306)] - fix: change `freeSocketTimeout` default value to 4000 (#102) (fengmk2 <>) 39 | 40 | **others** 41 | * [[`bc2a1ce`](http://github.com/node-modules/agentkeepalive/commit/bc2a1cea0884b4d18b0d244bf00006d9107963df)] - doc(readme): making `timeout`'s default clear (#100) (Aaron <>) 42 | 43 | 4.1.4 / 2021-02-05 44 | ================== 45 | 46 | **fixes** 47 | * [[`4d04794`](http://github.com/node-modules/agentkeepalive/commit/4d047946b1547b4edff92ea40205aee4f0c8aa46)] - fix(types): correct `Https` constructor argument (#89) (Simen Bekkhus <>) 48 | 49 | 4.1.3 / 2020-06-15 50 | ================== 51 | 52 | **fixes** 53 | * [[`4ba9f9c`](http://github.com/node-modules/agentkeepalive/commit/4ba9f9c844f2a6b8037ce56599d25c69ef054d91)] - fix: compatible with node v12.16.3 (#91) (killa <>) 54 | 55 | 4.1.2 / 2020-04-25 56 | ================== 57 | 58 | **fixes** 59 | * [[`de66b02`](http://github.com/node-modules/agentkeepalive/commit/de66b0206d064a97129c2c31bcdabd4d64557b91)] - fix: detect http request timeout handler (#88) (fengmk2 <>) 60 | 61 | 4.1.1 / 2020-04-25 62 | ================== 63 | 64 | **fixes** 65 | * [[`bbd20c0`](http://github.com/node-modules/agentkeepalive/commit/bbd20c03b8cf7dfb00b3aad1ada26d4ab90d2d6e)] - fix: definition error (#87) (吖猩 <>) 66 | 67 | **others** 68 | * [[`3b01699`](http://github.com/node-modules/agentkeepalive/commit/3b01699b8e90022d5f56898dd709e4fe7ee7cdaa)] - test: run test on node 12 (#84) (Igor Savin <>) 69 | 70 | 4.1.0 / 2019-10-12 71 | ================== 72 | 73 | **features** 74 | * [[`fe33b80`](http://github.com/node-modules/agentkeepalive/commit/fe33b800acc09109388bfe65107550952b6fc7b0)] - feat: Add `reusedSocket` property on client request (#82) (Weijia Wang <>) 75 | 76 | **others** 77 | * [[`77ba744`](http://github.com/node-modules/agentkeepalive/commit/77ba744667bb6b9e5986a53e5222f62094db12b9)] - docs: fix grammar in readme (#81) (Herrington Darkholme <<2883231+HerringtonDarkholme@users.noreply.github.com>>) 78 | 79 | 4.0.2 / 2019-02-19 80 | ================== 81 | 82 | **fixes** 83 | * [[`56d4a9b`](http://github.com/node-modules/agentkeepalive/commit/56d4a9b2a4499ea28943ddb590358d7831a02cb1)] - fix: HttpAgent export = internal (#74) (Andrew Leedham <>) 84 | 85 | 4.0.1 / 2019-02-19 86 | ================== 87 | 88 | **fixes** 89 | * [[`bad1ac0`](http://github.com/node-modules/agentkeepalive/commit/bad1ac0e710fbc486717e14e68c59266d35df6a8)] - fix: HttpsAgent Type Definition (#71) (#72) (Andrew Leedham <>) 90 | * [[`f48a4a7`](http://github.com/node-modules/agentkeepalive/commit/f48a4a701ea6fbe43781c91e1c0aaad6e328ac7f)] - fix: export interface (#70) (Vinay <>) 91 | 92 | **others** 93 | * [[`9124343`](http://github.com/node-modules/agentkeepalive/commit/91243437cfdd324cb97f39dee76746d5e5f4cd72)] - chore: add agent.options.keepAlive instead agent.keepAlive (fengmk2 <>) 94 | * [[`d177d40`](http://github.com/node-modules/agentkeepalive/commit/d177d40422fe7296990b4e270cf498e3f33c18fa)] - test: add request timeout bigger than agent timeout cases (fengmk2 <>) 95 | 96 | 4.0.0 / 2018-10-23 97 | ================== 98 | 99 | **features** 100 | * [[`5c9f3bb`](http://github.com/node-modules/agentkeepalive/commit/5c9f3bbd60555744edcf777105b148982a1a42b6)] - feat: impl the new Agent extend http.Agent (fengmk2 <>) 101 | 102 | **others** 103 | * [[`498c8f1`](http://github.com/node-modules/agentkeepalive/commit/498c8f13cf76600d3dd6e1c91cdf2d8292355dff)] - chore: move LICENSE from readme to file (fengmk2 <>) 104 | * [[`4f39894`](http://github.com/node-modules/agentkeepalive/commit/4f398942ba2f90cf4501239e56ac4e6344931a01)] - bugfix: support agent.options.timeout on https agent (fengmk2 <>) 105 | 106 | 3.5.2 / 2018-10-19 107 | ================== 108 | 109 | **fixes** 110 | * [[`5751fc1`](http://github.com/node-modules/agentkeepalive/commit/5751fc1180ed6544602c681ffbd08ca66a0cb12c)] - fix: sockLen being miscalculated when removing sockets (#60) (Ehden Sinai <>) 111 | 112 | 3.5.1 / 2018-07-31 113 | ================== 114 | 115 | **fixes** 116 | * [[`495f1ab`](http://github.com/node-modules/agentkeepalive/commit/495f1ab625d43945d72f68096b97db723d4f0657)] - fix: add the lost npm files (#66) (Henry Zhuang <>) 117 | 118 | 3.5.0 / 2018-07-31 119 | ================== 120 | 121 | **features** 122 | * [[`16f5aea`](http://github.com/node-modules/agentkeepalive/commit/16f5aeadfda57f1c602652f1472a63cc83cd05bf)] - feat: add typing define. (#65) (Henry Zhuang <>) 123 | 124 | **others** 125 | * [[`28fa062`](http://github.com/node-modules/agentkeepalive/commit/28fa06246fb5103f88ebeeb8563757a9078b8157)] - docs: add "per host" to description of maxFreeSockets (tony-gutierrez <>) 126 | * [[`7df2577`](http://github.com/node-modules/agentkeepalive/commit/7df25774f00a1031ca4daad2878a17e0539072a2)] - test: run test on node 10 (#63) (fengmk2 <>) 127 | 128 | 3.4.1 / 2018-03-08 129 | ================== 130 | 131 | **fixes** 132 | * [[`4d3a3b1`](http://github.com/node-modules/agentkeepalive/commit/4d3a3b1f7b16595febbbd39eeed72b2663549014)] - fix: Handle ipv6 addresses in host-header correctly with TLS (#53) (Mattias Holmlund <>) 133 | 134 | **others** 135 | * [[`55a7a5c`](http://github.com/node-modules/agentkeepalive/commit/55a7a5cd33e97f9a8370083dcb041c5552f10ac9)] - test: stop timer after test end (fengmk2 <>) 136 | 137 | 3.4.0 / 2018-02-27 138 | ================== 139 | 140 | **features** 141 | * [[`bc7cadb`](http://github.com/node-modules/agentkeepalive/commit/bc7cadb30ecd2071e2b341ac53ae1a2b8155c43d)] - feat: use socket custom freeSocketKeepAliveTimeout first (#59) (fengmk2 <>) 142 | 143 | **others** 144 | * [[`138eda8`](http://github.com/node-modules/agentkeepalive/commit/138eda81e10b632aaa87bea0cb66d8667124c4e8)] - doc: fix `keepAliveMsecs` params description (#55) (Hongcai Deng <>) 145 | 146 | 3.3.0 / 2017-06-20 147 | ================== 148 | 149 | * feat: add statusChanged getter (#51) 150 | * chore: format License 151 | 152 | 3.2.0 / 2017-06-10 153 | ================== 154 | 155 | * feat: add expiring active sockets 156 | * test: add node 8 (#49) 157 | 158 | 3.1.0 / 2017-02-20 159 | ================== 160 | 161 | * feat: timeout support humanize ms (#48) 162 | 163 | 3.0.0 / 2016-12-20 164 | ================== 165 | 166 | * fix: emit agent socket close event 167 | * test: add remove excess calls to removeSocket 168 | * test: use egg-ci 169 | * test: refactor test with eslint rules 170 | * feat: merge _http_agent.js from 7.2.1 171 | 172 | 2.2.0 / 2016-06-26 173 | ================== 174 | 175 | * feat: Add browser shim (noop) for isomorphic use. (#39) 176 | * chore: add security check badge 177 | 178 | 2.1.1 / 2016-04-06 179 | ================== 180 | 181 | * https: fix ssl socket leak when keepalive is used 182 | * chore: remove circle ci image 183 | 184 | 2.1.0 / 2016-04-02 185 | ================== 186 | 187 | * fix: opened sockets number overflow maxSockets 188 | 189 | 2.0.5 / 2016-03-16 190 | ================== 191 | 192 | * fix: pick _evictSession to httpsAgent 193 | 194 | 2.0.4 / 2016-03-13 195 | ================== 196 | 197 | * test: add Circle ci 198 | * test: add appveyor ci build 199 | * refactor: make sure only one error listener 200 | * chore: use codecov 201 | * fix: handle idle socket error 202 | * test: run on more node versions 203 | 204 | 2.0.3 / 2015-08-03 205 | ================== 206 | 207 | * fix: add default error handler to avoid Unhandled error event throw 208 | 209 | 2.0.2 / 2015-04-25 210 | ================== 211 | 212 | * fix: remove socket from freeSockets on 'timeout' (@pmalouin) 213 | 214 | 2.0.1 / 2015-04-19 215 | ================== 216 | 217 | * fix: add timeoutSocketCount to getCurrentStatus() 218 | * feat(getCurrentStatus): add getCurrentStatus 219 | 220 | 2.0.0 / 2015-04-01 221 | ================== 222 | 223 | * fix: socket.destroyed always be undefined on 0.10.x 224 | * Make it compatible with node v0.10.x (@lattmann) 225 | 226 | 1.2.1 / 2015-03-23 227 | ================== 228 | 229 | * patch from iojs: don't overwrite servername option 230 | * patch commits from joyent/node 231 | * add max sockets test case 232 | * add nagle algorithm delayed link 233 | 234 | 1.2.0 / 2014-09-02 235 | ================== 236 | 237 | * allow set keepAliveTimeout = 0 238 | * support timeout on working socket. fixed #6 239 | 240 | 1.1.0 / 2014-08-28 241 | ================== 242 | 243 | * add some socket counter for deep monitor 244 | 245 | 1.0.0 / 2014-08-13 246 | ================== 247 | 248 | * update _http_agent, only support 0.11+, only support node 0.11.0+ 249 | 250 | 0.2.2 / 2013-11-19 251 | ================== 252 | 253 | * support node 0.8 and node 0.10 254 | 255 | 0.2.1 / 2013-11-08 256 | ================== 257 | 258 | * fix socket does not timeout bug, it will hang on life, must use 0.2.x on node 0.11 259 | 260 | 0.2.0 / 2013-11-06 261 | ================== 262 | 263 | * use keepalive agent on node 0.11+ impl 264 | 265 | 0.1.5 / 2013-06-24 266 | ================== 267 | 268 | * support coveralls 269 | * add node 0.10 test 270 | * add 0.8.22 original https.js 271 | * add original http.js module to diff 272 | * update jscover 273 | * mv pem to fixtures 274 | * add https agent usage 275 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright(c) node-modules and other contributors. 4 | Copyright(c) 2012 - 2015 fengmk2 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agentkeepalive 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Known Vulnerabilities][snyk-image]][snyk-url] 5 | [![Node.js CI](https://github.com/node-modules/agentkeepalive/actions/workflows/nodejs.yml/badge.svg)](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 | 245 | 246 | ## Contributors 247 | 248 | |[
fengmk2](https://github.com/fengmk2)
|[
dead-horse](https://github.com/dead-horse)
|[
AndrewLeedham](https://github.com/AndrewLeedham)
|[
ngot](https://github.com/ngot)
|[
wrynearson](https://github.com/wrynearson)
|[
aaronArinder](https://github.com/aaronArinder)
| 249 | | :---: | :---: | :---: | :---: | :---: | :---: | 250 | |[
alexpenev-s](https://github.com/alexpenev-s)
|[
blemoine](https://github.com/blemoine)
|[
bdehamer](https://github.com/bdehamer)
|[
DylanPiercey](https://github.com/DylanPiercey)
|[
cixel](https://github.com/cixel)
|[
HerringtonDarkholme](https://github.com/HerringtonDarkholme)
| 251 | |[
denghongcai](https://github.com/denghongcai)
|[
kibertoad](https://github.com/kibertoad)
|[
pangorgo](https://github.com/pangorgo)
|[
mattiash](https://github.com/mattiash)
|[
nabeelbukhari](https://github.com/nabeelbukhari)
|[
pmalouin](https://github.com/pmalouin)
| 252 | [
SimenB](https://github.com/SimenB)
|[
vinaybedre](https://github.com/vinaybedre)
|[
starkwang](https://github.com/starkwang)
|[
killagu](https://github.com/killagu)
|[
tony-gutierrez](https://github.com/tony-gutierrez)
|[
whxaxes](https://github.com/whxaxes)
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 | 257 | -------------------------------------------------------------------------------- /benchmark/echo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const HttpAgent = require('../').HttpAgent; 4 | 5 | const agent = new HttpAgent({ 6 | keepAlive: true, 7 | maxSockets: 2, 8 | maxFreeSockets: 2, 9 | keepAliveMsecs: 30000, 10 | }); 11 | 12 | const options = { 13 | host: '10.125.196.152', 14 | port: 1984, 15 | path: '/1', 16 | method: 'GET', 17 | }; 18 | 19 | function get() { 20 | agent.get(options, res => { 21 | let size = 0; 22 | res.on('data', c => { 23 | size += c.length; 24 | }).on('end', () => { 25 | console.log('got %d bytes', size); 26 | }); 27 | }).on('error', err => { 28 | console.log('got error: %s', err); 29 | }); 30 | } 31 | 32 | setInterval(() => { 33 | get(); 34 | get(); 35 | }, 1000); 36 | get(); 37 | 38 | 39 | function showAgentDetail() { 40 | const peddingRequests = {}; 41 | for (const k in agent.requests) { 42 | const reqs = agent.requests[k]; 43 | peddingRequests[k] = reqs && reqs.length || 0; 44 | } 45 | const totalSockets = {}; 46 | for (const k in agent.sockets) { 47 | const socks = agent.sockets[k]; 48 | totalSockets[k] = socks && socks.length || 0; 49 | } 50 | const freeSockets = {}; 51 | for (const k in agent.freeSockets) { 52 | const socks = agent.freeSockets[k]; 53 | freeSockets[k] = socks && socks.length || 0; 54 | } 55 | 56 | const requestPerSocket = agent.createSocketCount && agent.requestFinishedCount / agent.createSocketCount; 57 | console.log('[%s] [worker:%d] HttpAgent(%s,%sms,%s,%s): requests: %d, created: %d, timeout: %d, reqs/socket: %s, pedding requests: %j, alive sockets: %j, free sockets: %j', 58 | Date(), process.pid, 59 | agent.keepAlive && agent.options.keepAlive, agent.keepAliveMsecs, 60 | agent.maxSockets, agent.maxFreeSockets, 61 | agent.requestFinishedCount, agent.createSocketCount, agent.timeoutSocketCount, 62 | requestPerSocket.toFixed(0), 63 | peddingRequests, totalSockets, freeSockets 64 | ); 65 | } 66 | 67 | setInterval(showAgentDetail, 3000); 68 | -------------------------------------------------------------------------------- /benchmark/proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var http = require('http'); 4 | var HttpAgent = require('../').HttpAgent; 5 | 6 | var maxSockets = parseInt(process.argv[2]) || 10; 7 | var maxFreeSockets = parseInt(process.argv[3]) || maxSockets; 8 | var SERVER = process.argv[4] || '127.0.0.1'; 9 | 10 | var agentKeepalive = new HttpAgent({ 11 | keepAlive: true, 12 | maxSockets: maxSockets, 13 | maxFreeSockets: maxFreeSockets, 14 | keepAliveTimeout: 30000, 15 | }); 16 | var agentHttp = new HttpAgent({ 17 | maxSockets: maxSockets, 18 | keepAlive: false, 19 | }); 20 | 21 | var count = 0; 22 | var rtKeepalives = { 23 | ' <10ms': 0, 24 | ' <15ms': 0, 25 | ' <20ms': 0, 26 | ' <30ms': 0, 27 | ' <40ms': 0, 28 | ' <50ms': 0, 29 | ' <100ms': 0, 30 | ' <150ms': 0, 31 | ' <200ms': 0, 32 | ' >=200ms+': 0 33 | }; 34 | 35 | var rtNormals = { 36 | ' <10ms': 0, 37 | ' <15ms': 0, 38 | ' <20ms': 0, 39 | ' <30ms': 0, 40 | ' <40ms': 0, 41 | ' <50ms': 0, 42 | ' <100ms': 0, 43 | ' <150ms': 0, 44 | ' <200ms': 0, 45 | ' >=200ms+': 0 46 | }; 47 | 48 | setInterval(function () { 49 | console.log('\n\n----------------------------------------------------------------'); 50 | console.log('[proxy.js:%d] keepalive, %d created, %d requests, %d req/socket, %d close, %d timeout\n%j', 51 | count, 52 | agentKeepalive.createSocketCount, 53 | agentKeepalive.requestCount, 54 | (agentKeepalive.requestCount / agentKeepalive.createSocketCount || 0).toFixed(2), 55 | agentKeepalive.closeSocketCount, 56 | agentKeepalive.timeoutSocketCount, 57 | rtKeepalives 58 | ); 59 | for (var name in agentKeepalive.sockets) { 60 | console.log('sockets %s: %d', name, agentKeepalive.sockets[name].length); 61 | } 62 | for (var name in agentKeepalive.freeSockets) { 63 | console.log('freeSockets %s: %d', name, 64 | agentKeepalive.freeSockets[name].length || 0); 65 | } 66 | for (var name in agentKeepalive.requests) { 67 | console.log('requests %s: %d', name, 68 | agentKeepalive.requests[name].length || 0); 69 | } 70 | 71 | console.log('----------------------------------------------------------------'); 72 | console.log('[proxy.js:%d] normal , %d created, %d requests, %d req/socket, %d close\n%j', 73 | count, 74 | agentHttp.createSocketCount, 75 | agentHttp.requestCount, 76 | (agentHttp.requestCount / agentHttp.createSocketCount || 0).toFixed(2), 77 | agentHttp.closeSocketCount, 78 | rtNormals 79 | ); 80 | for (var name in agentHttp.sockets) { 81 | console.log('sockets %s: %d', name, agentHttp.sockets[name].length); 82 | } 83 | for (var name in agentHttp.freeSockets) { 84 | console.log('freeSockets %s: %d', name, 85 | agentHttp.freeSockets[name].length || 0); 86 | } 87 | for (var name in agentHttp.requests) { 88 | console.log('requests %s: %d', name, 89 | agentHttp.requests[name].length || 0); 90 | } 91 | }, 2000); 92 | 93 | http.createServer(function (req, res) { 94 | var path = req.url; 95 | var agent = agentHttp; 96 | var method = 'GET'; 97 | var postData = null; 98 | var rts = rtNormals; 99 | if (path.indexOf('/post') === 0) { 100 | path = path.substring(5); 101 | method = 'POST'; 102 | postData = '{"sql":"select id from keepalive where age between 1 and 1000 and count between 1 and 100 order by id desc limit 2","data":null}'; 103 | } 104 | if (path.indexOf('/k') === 0) { 105 | path = path.substring(2); 106 | agent = agentKeepalive; 107 | rts = rtKeepalives; 108 | } 109 | var options = { 110 | host: SERVER, 111 | port: 1984, 112 | path: path, 113 | method: method, 114 | agent: agent 115 | }; 116 | req.on('data', function () {}); 117 | req.on('end', function () { 118 | var timer = null; 119 | var start = Date.now(); 120 | var client = http.request(options, function (response) { 121 | response.pipe(res); 122 | response.once('end', function () { 123 | var use = Date.now() - start; 124 | if (use < 10) { 125 | rts[' <10ms']++; 126 | } else if (use < 15) { 127 | rts[' <15ms']++; 128 | } else if (use < 20) { 129 | rts[' <20ms']++; 130 | } else if (use < 30) { 131 | rts[' <30ms']++; 132 | } else if (use < 40) { 133 | rts[' <40ms']++; 134 | } else if (use < 50) { 135 | rts[' <50ms']++; 136 | } else if (use < 100) { 137 | rts[' <100ms']++; 138 | } else if (use < 150) { 139 | rts[' <150ms']++; 140 | } else if (use < 200) { 141 | rts[' <200ms']++; 142 | } else { 143 | rts[' >=200ms+']++; 144 | } 145 | if (timer) { 146 | clearTimeout(timer); 147 | timer = null; 148 | } 149 | count++; 150 | }); 151 | }); 152 | client.on('error', function (err) { 153 | // console.log('error ' + req.url + ':' + err.message); 154 | res.statusCode = 500; 155 | res.end(err.message); 156 | }); 157 | timer = setTimeout(function () { 158 | // console.log('2000ms timeout ' + req.url); 159 | timer = null; 160 | client.abort(); 161 | }, 2000); 162 | client.end(postData); 163 | }); 164 | }).listen(1985); 165 | 166 | console.log('proxy start, listen on 1985'); 167 | -------------------------------------------------------------------------------- /benchmark/readme.md: -------------------------------------------------------------------------------- 1 | # Benchmark result 2 | 3 | Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz 4 | 5 | node@v0.8.9 6 | 7 | 50 maxSockets, 60 concurrent, 1000 requests per concurrent, 5ms delay 8 | 9 | Keep alive agent (30 seconds): 10 | 11 | ```js 12 | Transactions: 60000 hits 13 | Availability: 100.00 % 14 | Elapsed time: 29.70 secs 15 | Data transferred: 14.88 MB 16 | Response time: 0.03 secs 17 | Transaction rate: 2020.20 trans/sec 18 | Throughput: 0.50 MB/sec 19 | Concurrency: 59.84 20 | Successful transactions: 60000 21 | Failed transactions: 0 22 | Longest transaction: 0.15 23 | Shortest transaction: 0.01 24 | ``` 25 | 26 | Normal agent: 27 | 28 | ```js 29 | Transactions: 60000 hits 30 | Availability: 100.00 % 31 | Elapsed time: 46.53 secs 32 | Data transferred: 14.88 MB 33 | Response time: 0.05 secs 34 | Transaction rate: 1289.49 trans/sec 35 | Throughput: 0.32 MB/sec 36 | Concurrency: 59.81 37 | Successful transactions: 60000 38 | Failed transactions: 0 39 | Longest transaction: 0.45 40 | Shortest transaction: 0.00 41 | ``` 42 | 43 | Socket created: 44 | 45 | ``` 46 | [proxy.js:120000] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 47 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 48 | ---------------------------------------------------------------- 49 | [proxy.js:120000] normal , 53866 created, 84260 requestFinished, 1.56 req/socket, 0 requests, 0 sockets 50 | {" <10ms":75," <15ms":1112," <20ms":10947," <30ms":32130," <40ms":8228," <50ms":3002," <100ms":4274," <150ms":181," <200ms":18," >=200ms+":33} 51 | ``` 52 | 53 | ```bash 54 | $ sh start.sh 55 | net.inet.ip.portrange.first: 12000 -> 12000 56 | net.inet.tcp.msl: 1000 -> 1000 57 | kern.maxfiles: 1000000 -> 1000000 58 | kern.maxfilesperproc: 1000000 -> 1000000 59 | Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz 60 | proxy start, listen on 1985 61 | sleep server start, listen on 1984 62 | v0.8.9 63 | 50 maxSockets, 60 concurrent, 1000 requests per concurrent, 5ms delay 64 | keep alive 65 | siege -c 60 -r 1000 -b http://localhost:1985/post/k/5 66 | ** SIEGE 2.72 67 | ** Preparing 60 concurrent users for battle. 68 | The server is now under siege...---------------------------------------------------------------- 69 | [proxy.js:1600] keepalive, 50 created, 1600 requestFinished, 32 req/socket, 0 requests, 50 sockets, 8 unusedSockets, 0 timeout 70 | {" <10ms":4," <15ms":334," <20ms":477," <30ms":502," <40ms":121," <50ms":35," <100ms":127," <150ms":0," <200ms":0," >=200ms+":0} 71 | ---------------------------------------------------------------- 72 | [proxy.js:1600] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 73 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 74 | ---------------------------------------------------------------- 75 | [proxy.js:5629] keepalive, 50 created, 5629 requestFinished, 112.58 req/socket, 0 requests, 50 sockets, 9 unusedSockets, 0 timeout 76 | {" <10ms":46," <15ms":1333," <20ms":1875," <30ms":1790," <40ms":365," <50ms":73," <100ms":147," <150ms":0," <200ms":0," >=200ms+":0} 77 | ---------------------------------------------------------------- 78 | [proxy.js:5629] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 79 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 80 | ---------------------------------------------------------------- 81 | [proxy.js:9926] keepalive, 50 created, 9926 requestFinished, 198.52 req/socket, 0 requests, 50 sockets, 11 unusedSockets, 0 timeout 82 | {" <10ms":98," <15ms":2718," <20ms":3311," <30ms":3046," <40ms":489," <50ms":96," <100ms":168," <150ms":0," <200ms":0," >=200ms+":0} 83 | ---------------------------------------------------------------- 84 | [proxy.js:9926] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 85 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 86 | ---------------------------------------------------------------- 87 | [proxy.js:14042] keepalive, 50 created, 14042 requestFinished, 280.84 req/socket, 0 requests, 50 sockets, 1 unusedSockets, 0 timeout 88 | {" <10ms":136," <15ms":3919," <20ms":4787," <30ms":4222," <40ms":652," <50ms":145," <100ms":181," <150ms":0," <200ms":0," >=200ms+":0} 89 | ---------------------------------------------------------------- 90 | [proxy.js:14042] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 91 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 92 | ---------------------------------------------------------------- 93 | [proxy.js:18233] keepalive, 50 created, 18233 requestFinished, 364.66 req/socket, 0 requests, 50 sockets, 17 unusedSockets, 0 timeout 94 | {" <10ms":173," <15ms":5239," <20ms":6318," <30ms":5401," <40ms":735," <50ms":171," <100ms":196," <150ms":0," <200ms":0," >=200ms+":0} 95 | ---------------------------------------------------------------- 96 | [proxy.js:18233] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 97 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 98 | ---------------------------------------------------------------- 99 | [proxy.js:22350] keepalive, 50 created, 22350 requestFinished, 447 req/socket, 0 requests, 50 sockets, 6 unusedSockets, 0 timeout 100 | {" <10ms":203," <15ms":6482," <20ms":7665," <30ms":6716," <40ms":859," <50ms":206," <100ms":219," <150ms":0," <200ms":0," >=200ms+":0} 101 | ---------------------------------------------------------------- 102 | [proxy.js:22350] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 103 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 104 | ---------------------------------------------------------------- 105 | [proxy.js:26373] keepalive, 50 created, 26373 requestFinished, 527.46 req/socket, 0 requests, 50 sockets, 11 unusedSockets, 0 timeout 106 | {" <10ms":221," <15ms":7790," <20ms":9185," <30ms":7631," <40ms":1042," <50ms":249," <100ms":255," <150ms":0," <200ms":0," >=200ms+":0} 107 | ---------------------------------------------------------------- 108 | [proxy.js:26373] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 109 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 110 | ---------------------------------------------------------------- 111 | [proxy.js:30501] keepalive, 50 created, 30501 requestFinished, 610.02 req/socket, 0 requests, 50 sockets, 15 unusedSockets, 0 timeout 112 | {" <10ms":248," <15ms":9095," <20ms":10715," <30ms":8715," <40ms":1204," <50ms":256," <100ms":268," <150ms":0," <200ms":0," >=200ms+":0} 113 | ---------------------------------------------------------------- 114 | [proxy.js:30501] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 115 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 116 | ---------------------------------------------------------------- 117 | [proxy.js:33819] keepalive, 50 created, 33819 requestFinished, 676.38 req/socket, 0 requests, 50 sockets, 13 unusedSockets, 0 timeout 118 | {" <10ms":303," <15ms":9768," <20ms":11677," <30ms":9749," <40ms":1571," <50ms":368," <100ms":327," <150ms":56," <200ms":0," >=200ms+":0} 119 | ---------------------------------------------------------------- 120 | [proxy.js:33819] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 121 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 122 | ---------------------------------------------------------------- 123 | [proxy.js:38097] keepalive, 50 created, 38097 requestFinished, 761.94 req/socket, 0 requests, 50 sockets, 5 unusedSockets, 0 timeout 124 | {" <10ms":355," <15ms":11127," <20ms":13253," <30ms":10940," <40ms":1653," <50ms":386," <100ms":327," <150ms":56," <200ms":0," >=200ms+":0} 125 | ---------------------------------------------------------------- 126 | [proxy.js:38097] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 127 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 128 | ---------------------------------------------------------------- 129 | [proxy.js:42325] keepalive, 50 created, 42325 requestFinished, 846.5 req/socket, 0 requests, 50 sockets, 10 unusedSockets, 0 timeout 130 | {" <10ms":399," <15ms":12507," <20ms":14682," <30ms":12236," <40ms":1720," <50ms":398," <100ms":327," <150ms":56," <200ms":0," >=200ms+":0} 131 | ---------------------------------------------------------------- 132 | [proxy.js:42325] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 133 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 134 | ---------------------------------------------------------------- 135 | [proxy.js:46568] keepalive, 50 created, 46568 requestFinished, 931.36 req/socket, 0 requests, 50 sockets, 3 unusedSockets, 0 timeout 136 | {" <10ms":443," <15ms":13947," <20ms":16180," <30ms":13421," <40ms":1780," <50ms":414," <100ms":327," <150ms":56," <200ms":0," >=200ms+":0} 137 | ---------------------------------------------------------------- 138 | [proxy.js:46568] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 139 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 140 | ---------------------------------------------------------------- 141 | [proxy.js:50666] keepalive, 50 created, 50666 requestFinished, 1013.32 req/socket, 0 requests, 50 sockets, 3 unusedSockets, 0 timeout 142 | {" <10ms":495," <15ms":15152," <20ms":17470," <30ms":14774," <40ms":1924," <50ms":463," <100ms":332," <150ms":56," <200ms":0," >=200ms+":0} 143 | ---------------------------------------------------------------- 144 | [proxy.js:50666] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 145 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 146 | ---------------------------------------------------------------- 147 | [proxy.js:54801] keepalive, 50 created, 54801 requestFinished, 1096.02 req/socket, 0 requests, 50 sockets, 14 unusedSockets, 0 timeout 148 | {" <10ms":540," <15ms":16484," <20ms":18745," <30ms":16066," <40ms":2059," <50ms":499," <100ms":352," <150ms":56," <200ms":0," >=200ms+":0} 149 | ---------------------------------------------------------------- 150 | [proxy.js:54801] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 151 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 152 | ---------------------------------------------------------------- 153 | [proxy.js:58861] keepalive, 50 created, 58861 requestFinished, 1177.22 req/socket, 0 requests, 50 sockets, 14 unusedSockets, 0 timeout 154 | {" <10ms":574," <15ms":17515," <20ms":20286," <30ms":17305," <40ms":2217," <50ms":550," <100ms":358," <150ms":56," <200ms":0," >=200ms+":0} 155 | ---------------------------------------------------------------- 156 | [proxy.js:58861] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 157 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 158 | done. 159 | 160 | Transactions: 60000 hits 161 | Availability: 100.00 % 162 | Elapsed time: 29.70 secs 163 | Data transferred: 14.88 MB 164 | Response time: 0.03 secs 165 | Transaction rate: 2020.20 trans/sec 166 | Throughput: 0.50 MB/sec 167 | Concurrency: 59.84 168 | Successful transactions: 60000 169 | Failed transactions: 0 170 | Longest transaction: 0.15 171 | Shortest transaction: 0.01 172 | 173 | ---------------------------------------------------------------- 174 | [proxy.js:60000] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 175 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 176 | ---------------------------------------------------------------- 177 | [proxy.js:60000] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 178 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 179 | ---------------------------------------------------------------- 180 | [proxy.js:60000] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 181 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 182 | ---------------------------------------------------------------- 183 | [proxy.js:60000] normal , 0 created, 0 requestFinished, 0 req/socket, 0 requests, 0 sockets 184 | {" <10ms":0," <15ms":0," <20ms":0," <30ms":0," <40ms":0," <50ms":0," <100ms":0," <150ms":0," <200ms":0," >=200ms+":0} 185 | normal 186 | siege -c 60 -r 1000 -b http://localhost:1985/post/5 187 | ** SIEGE 2.72 188 | ** Preparing 60 concurrent users for battle. 189 | The server is now under siege...---------------------------------------------------------------- 190 | [proxy.js:60237] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 191 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 192 | ---------------------------------------------------------------- 193 | [proxy.js:60237] normal , 208 created, 286 requestFinished, 1.38 req/socket, 0 requests, 10 sockets 194 | {" <10ms":1," <15ms":0," <20ms":0," <30ms":23," <40ms":50," <50ms":64," <100ms":79," <150ms":20," <200ms":0," >=200ms+":0} 195 | ---------------------------------------------------------------- 196 | [proxy.js:62788] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 197 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 198 | ---------------------------------------------------------------- 199 | [proxy.js:62788] normal , 2578 created, 3899 requestFinished, 1.51 req/socket, 0 requests, 45 sockets 200 | {" <10ms":1," <15ms":12," <20ms":472," <30ms":1349," <40ms":469," <50ms":239," <100ms":186," <150ms":60," <200ms":0," >=200ms+":0} 201 | ---------------------------------------------------------------- 202 | [proxy.js:65594] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 203 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 204 | ---------------------------------------------------------------- 205 | [proxy.js:65594] normal , 5091 created, 7841 requestFinished, 1.54 req/socket, 0 requests, 33 sockets 206 | {" <10ms":1," <15ms":69," <20ms":1035," <30ms":2967," <40ms":765," <50ms":375," <100ms":322," <150ms":60," <200ms":0," >=200ms+":0} 207 | ---------------------------------------------------------------- 208 | [proxy.js:68371] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 209 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 210 | ---------------------------------------------------------------- 211 | [proxy.js:68371] normal , 7669 created, 11825 requestFinished, 1.54 req/socket, 0 requests, 31 sockets 212 | {" <10ms":4," <15ms":89," <20ms":1538," <30ms":4702," <40ms":1041," <50ms":444," <100ms":493," <150ms":60," <200ms":0," >=200ms+":0} 213 | ---------------------------------------------------------------- 214 | [proxy.js:71027] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 215 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 216 | ---------------------------------------------------------------- 217 | [proxy.js:71027] normal , 9974 created, 15498 requestFinished, 1.55 req/socket, 0 requests, 28 sockets 218 | {" <10ms":7," <15ms":164," <20ms":1955," <30ms":6209," <40ms":1414," <50ms":520," <100ms":686," <150ms":72," <200ms":0," >=200ms+":0} 219 | ---------------------------------------------------------------- 220 | [proxy.js:73665] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 221 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 222 | ---------------------------------------------------------------- 223 | [proxy.js:73665] normal , 12283 created, 19152 requestFinished, 1.56 req/socket, 0 requests, 33 sockets 224 | {" <10ms":9," <15ms":213," <20ms":2383," <30ms":7659," <40ms":1798," <50ms":635," <100ms":883," <150ms":85," <200ms":0," >=200ms+":0} 225 | ---------------------------------------------------------------- 226 | [proxy.js:75652] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 227 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 228 | ---------------------------------------------------------------- 229 | [proxy.js:75652] normal , 14101 created, 21974 requestFinished, 1.56 req/socket, 0 requests, 42 sockets 230 | {" <10ms":9," <15ms":221," <20ms":2527," <30ms":8311," <40ms":2354," <50ms":973," <100ms":1163," <150ms":94," <200ms":0," >=200ms+":0} 231 | ---------------------------------------------------------------- 232 | [proxy.js:78417] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 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:78417] normal , 16543 created, 25836 requestFinished, 1.56 req/socket, 0 requests, 36 sockets 236 | {" <10ms":14," <15ms":305," <20ms":2962," <30ms":9801," <40ms":2819," <50ms":1088," <100ms":1334," <150ms":94," <200ms":0," >=200ms+":0} 237 | ---------------------------------------------------------------- 238 | [proxy.js:80952] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 239 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 240 | ---------------------------------------------------------------- 241 | [proxy.js:80952] normal , 18889 created, 29468 requestFinished, 1.56 req/socket, 0 requests, 43 sockets 242 | {" <10ms":14," <15ms":356," <20ms":3517," <30ms":11159," <40ms":3108," <50ms":1157," <100ms":1503," <150ms":120," <200ms":18," >=200ms+":0} 243 | ---------------------------------------------------------------- 244 | [proxy.js:83686] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 245 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 246 | ---------------------------------------------------------------- 247 | [proxy.js:83686] normal , 21300 created, 33301 requestFinished, 1.56 req/socket, 0 requests, 38 sockets 248 | {" <10ms":16," <15ms":420," <20ms":4025," <30ms":12597," <40ms":3512," <50ms":1308," <100ms":1670," <150ms":120," <200ms":18," >=200ms+":0} 249 | ---------------------------------------------------------------- 250 | [proxy.js:86108] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 251 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 252 | ---------------------------------------------------------------- 253 | [proxy.js:86108] normal , 23409 created, 36655 requestFinished, 1.57 req/socket, 0 requests, 22 sockets 254 | {" <10ms":18," <15ms":467," <20ms":4383," <30ms":13764," <40ms":3879," <50ms":1441," <100ms":2016," <150ms":122," <200ms":18," >=200ms+":0} 255 | ---------------------------------------------------------------- 256 | [proxy.js:88868] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 257 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 258 | ---------------------------------------------------------------- 259 | [proxy.js:88868] normal , 25814 created, 40482 requestFinished, 1.57 req/socket, 0 requests, 46 sockets 260 | {" <10ms":19," <15ms":533," <20ms":4902," <30ms":15201," <40ms":4275," <50ms":1587," <100ms":2211," <150ms":122," <200ms":18," >=200ms+":0} 261 | ---------------------------------------------------------------- 262 | [proxy.js:91666] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 50 sockets, 50 unusedSockets, 0 timeout 263 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 264 | ---------------------------------------------------------------- 265 | [proxy.js:91666] normal , 28381 created, 44473 requestFinished, 1.57 req/socket, 0 requests, 50 sockets 266 | {" <10ms":19," <15ms":554," <20ms":5545," <30ms":16824," <40ms":4529," <50ms":1706," <100ms":2349," <150ms":122," <200ms":18," >=200ms+":0} 267 | ---------------------------------------------------------------- 268 | [proxy.js:94360] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 269 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 270 | ---------------------------------------------------------------- 271 | [proxy.js:94360] normal , 30806 created, 48274 requestFinished, 1.57 req/socket, 0 requests, 38 sockets 272 | {" <10ms":20," <15ms":602," <20ms":6022," <30ms":18432," <40ms":4797," <50ms":1819," <100ms":2528," <150ms":122," <200ms":18," >=200ms+":0} 273 | ---------------------------------------------------------------- 274 | [proxy.js:97025] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 275 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 276 | ---------------------------------------------------------------- 277 | [proxy.js:97025] normal , 33198 created, 52025 requestFinished, 1.57 req/socket, 0 requests, 45 sockets 278 | {" <10ms":20," <15ms":656," <20ms":6538," <30ms":19831," <40ms":5199," <50ms":1936," <100ms":2700," <150ms":127," <200ms":18," >=200ms+":0} 279 | ---------------------------------------------------------------- 280 | [proxy.js:99476] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 281 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 282 | ---------------------------------------------------------------- 283 | [proxy.js:99476] normal , 35422 created, 55496 requestFinished, 1.57 req/socket, 0 requests, 50 sockets 284 | {" <10ms":20," <15ms":691," <20ms":6957," <30ms":20987," <40ms":5591," <50ms":2119," <100ms":2947," <150ms":146," <200ms":18," >=200ms+":0} 285 | ---------------------------------------------------------------- 286 | [proxy.js:102244] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 287 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 288 | ---------------------------------------------------------------- 289 | [proxy.js:102244] normal , 37970 created, 59456 requestFinished, 1.57 req/socket, 0 requests, 39 sockets 290 | {" <10ms":20," <15ms":724," <20ms":7531," <30ms":22621," <40ms":5963," <50ms":2201," <100ms":3008," <150ms":158," <200ms":18," >=200ms+":0} 291 | ---------------------------------------------------------------- 292 | [proxy.js:104983] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 293 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 294 | ---------------------------------------------------------------- 295 | [proxy.js:104983] normal , 40458 created, 63337 requestFinished, 1.57 req/socket, 0 requests, 45 sockets 296 | {" <10ms":23," <15ms":765," <20ms":8094," <30ms":24200," <40ms":6253," <50ms":2323," <100ms":3149," <150ms":158," <200ms":18," >=200ms+":0} 297 | ---------------------------------------------------------------- 298 | [proxy.js:107668] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 299 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 300 | ---------------------------------------------------------------- 301 | [proxy.js:107668] normal , 42842 created, 67108 requestFinished, 1.57 req/socket, 0 requests, 38 sockets 302 | {" <10ms":24," <15ms":819," <20ms":8633," <30ms":25651," <40ms":6577," <50ms":2419," <100ms":3369," <150ms":158," <200ms":18," >=200ms+":0} 303 | ---------------------------------------------------------------- 304 | [proxy.js:110199] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 305 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 306 | ---------------------------------------------------------------- 307 | [proxy.js:110199] normal , 45166 created, 70731 requestFinished, 1.57 req/socket, 0 requests, 47 sockets 308 | {" <10ms":26," <15ms":865," <20ms":9193," <30ms":26962," <40ms":6891," <50ms":2525," <100ms":3546," <150ms":173," <200ms":18," >=200ms+":0} 309 | ---------------------------------------------------------------- 310 | [proxy.js:112975] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 311 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 312 | ---------------------------------------------------------------- 313 | [proxy.js:112975] normal , 47579 created, 74565 requestFinished, 1.57 req/socket, 0 requests, 37 sockets 314 | {" <10ms":26," <15ms":935," <20ms":9745," <30ms":28452," <40ms":7222," <50ms":2639," <100ms":3765," <150ms":173," <200ms":18," >=200ms+":0} 315 | ---------------------------------------------------------------- 316 | [proxy.js:114637] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 317 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 318 | ---------------------------------------------------------------- 319 | [proxy.js:114637] normal , 48998 created, 76839 requestFinished, 1.57 req/socket, 0 requests, 23 sockets 320 | {" <10ms":26," <15ms":950," <20ms":9898," <30ms":29096," <40ms":7569," <50ms":2829," <100ms":4037," <150ms":181," <200ms":18," >=200ms+":33} 321 | ---------------------------------------------------------------- 322 | [proxy.js:117458] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 323 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 324 | ---------------------------------------------------------------- 325 | [proxy.js:117458] normal , 51580 created, 80856 requestFinished, 1.57 req/socket, 0 requests, 26 sockets 326 | {" <10ms":27," <15ms":981," <20ms":10458," <30ms":30768," <40ms":7952," <50ms":2941," <100ms":4099," <150ms":181," <200ms":18," >=200ms+":33} 327 | done. 328 | 329 | Transactions: 60000 hits 330 | Availability: 100.00 % 331 | Elapsed time: 46.53 secs 332 | Data transferred: 14.88 MB 333 | Response time: 0.05 secs 334 | Transaction rate: 1289.49 trans/sec 335 | Throughput: 0.32 MB/sec 336 | Concurrency: 59.81 337 | Successful transactions: 60000 338 | Failed transactions: 0 339 | Longest transaction: 0.45 340 | Shortest transaction: 0.00 341 | 342 | ---------------------------------------------------------------- 343 | [proxy.js:120000] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 344 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 345 | ---------------------------------------------------------------- 346 | [proxy.js:120000] normal , 53866 created, 84260 requestFinished, 1.56 req/socket, 0 requests, 0 sockets 347 | {" <10ms":75," <15ms":1112," <20ms":10947," <30ms":32130," <40ms":8228," <50ms":3002," <100ms":4274," <150ms":181," <200ms":18," >=200ms+":33} 348 | ---------------------------------------------------------------- 349 | [proxy.js:120000] keepalive, 50 created, 60000 requestFinished, 1200 req/socket, 0 requests, 0 sockets, 0 unusedSockets, 50 timeout 350 | {" <10ms":662," <15ms":17825," <20ms":20552," <30ms":17646," <40ms":2315," <50ms":567," <100ms":377," <150ms":56," <200ms":0," >=200ms+":0} 351 | ---------------------------------------------------------------- 352 | [proxy.js:120000] normal , 53866 created, 84260 requestFinished, 1.56 req/socket, 0 requests, 0 sockets 353 | {" <10ms":75," <15ms":1112," <20ms":10947," <30ms":32130," <40ms":8228," <50ms":3002," <100ms":4274," <150ms":181," <200ms":18," >=200ms+":33} 354 | ``` 355 | -------------------------------------------------------------------------------- /benchmark/siegerc: -------------------------------------------------------------------------------- 1 | # Updated by Siege 3.0.5, August-27-2014 2 | # Copyright 2000-2013 by Jeffrey Fulmer, et al. 3 | # 4 | # Siege configuration file -- edit as necessary 5 | # For more information about configuring and running 6 | # this program, visit: http://www.joedog.org/ 7 | 8 | # 9 | # Variable declarations. You can set variables here 10 | # for use in the directives below. Example: 11 | # PROXY = proxy.joedog.org 12 | # Reference variables inside ${} or $(), example: 13 | # proxy-host = ${PROXY} 14 | # You can also reference ENVIRONMENT variables without 15 | # actually declaring them, example: 16 | # logfile = $(HOME)/var/siege.log 17 | 18 | # 19 | # Verbose mode 20 | # 21 | # Signify verbose mode, true turns on verbose output 22 | # ex: verbose = true|false 23 | # 24 | verbose = false 25 | 26 | # 27 | # Quiet mode 28 | # 29 | # When true, this turns off verbose and standard output. 30 | # You'll still see the opening announcement and the final 31 | # stats if you're running a siege but -g/--get will be 32 | # extremely quiet. This was added primarily for scripting 33 | # ex: quiet = true|false 34 | # 35 | quiet = true 36 | 37 | # 38 | # Get method - select an HTTP method to use when siege 39 | # is set to get mode, siege -g/--get URL. You may select 40 | # GET or HEAD. The default method is HEAD. As expected 41 | # HEAD prints just the headers and GET prints the entire 42 | # page. 43 | # 44 | # NOTE: This only applies when siege is invoked with 45 | # -g/--get. All other requests methods will be made 46 | # on the basis of the URL. 47 | # 48 | # example: gmethod = GET 49 | # 50 | gmethod = HEAD 51 | 52 | 53 | # 54 | # CSV Verbose format: with this option, you can choose 55 | # to format verbose output in traditional siege format 56 | # or comma separated format. The latter will allow you 57 | # to redirect output to a file for import into a spread 58 | # sheet, i.e., siege > file.csv 59 | # ex: csv = true|false (default false) 60 | # 61 | # csv = true 62 | 63 | # 64 | # Timestamp format: with this option, you can choose to 65 | # print a timestamp each line of output 66 | # example: timestamp = true|false (default false) 67 | # 68 | # sample: [Sat, 2010-11-20 10:39:13] HTTP/1.1 200 0.12 secs: 4003 bytes ==> / 69 | # 70 | # timestamp = true 71 | 72 | # 73 | # Full URL verbose format: By default siege displays 74 | # the URL path and not the full URL. With this option, 75 | # you # can instruct siege to show the complete URL. 76 | # ex: fullurl = true|false (default false) 77 | # 78 | # fullurl = true 79 | 80 | # 81 | # Display id: in verbose mode, display the siege user 82 | # id associated with the HTTP transaction information 83 | # ex: display-id = true|false 84 | # 85 | # display-id = 86 | 87 | # 88 | # Show logfile location. By default, siege displays the 89 | # logfile location at the end of every run when logging 90 | # You can turn this message off with this directive. 91 | # ex: show-logfile = false 92 | # 93 | show-logfile = true 94 | 95 | # 96 | # Default logging status, true turns logging on. 97 | # ex: logging = true|false 98 | # 99 | logging = false 100 | 101 | # 102 | # Logfile, the default siege logfile is $PREFIX/var/siege.log 103 | # This directive allows you to choose an alternative log file. 104 | # Environment variables may be used as shown in the examples: 105 | # ex: logfile = /home/jeff/var/log/siege.log 106 | # logfile = ${HOME}/var/log/siege.log 107 | # logfile = ${LOGFILE} 108 | # 109 | # logfile = 110 | 111 | # 112 | # HTTP protocol. Options HTTP/1.1 and HTTP/1.0. 113 | # Some webservers have broken implementation of the 114 | # 1.1 protocol which skews throughput evaluations. 115 | # If you notice some siege clients hanging for 116 | # extended periods of time, change this to HTTP/1.0 117 | # ex: protocol = HTTP/1.1 118 | # protocol = HTTP/1.0 119 | # 120 | protocol = HTTP/1.1 121 | 122 | # 123 | # Chunked encoding is required by HTTP/1.1 protocol 124 | # but siege allows you to turn it off as desired. 125 | # 126 | # ex: chunked = true 127 | # 128 | chunked = true 129 | 130 | # 131 | # Cache revalidation. 132 | # Siege supports cache revalidation for both ETag and 133 | # Last-modified headers. If a copy is still fresh, the 134 | # server responds with 304. 135 | # HTTP/1.1 200 0.00 secs: 2326 bytes ==> /apache_pb.gif 136 | # HTTP/1.1 304 0.00 secs: 0 bytes ==> /apache_pb.gif 137 | # HTTP/1.1 304 0.00 secs: 0 bytes ==> /apache_pb.gif 138 | # 139 | # ex: cache = true 140 | # 141 | cache = false 142 | 143 | # 144 | # Connection directive. Options "close" and "keep-alive" 145 | # Starting with release 2.57b3, siege implements persistent 146 | # connections in accordance to RFC 2068 using both chunked 147 | # encoding and content-length directives to determine the 148 | # page size. To run siege with persistent connections set 149 | # the connection directive to keep-alive. (Default close) 150 | # CAUTION: use the keep-alive directive with care. 151 | # DOUBLE CAUTION: this directive does not work well on HPUX 152 | # TRIPLE CAUTION: don't use keep-alives until further notice 153 | # ex: connection = close 154 | # connection = keep-alive 155 | # 156 | connection = keep-alive 157 | 158 | # 159 | # Default number of simulated concurrent users 160 | # ex: concurrent = 25 161 | # 162 | concurrent = 15 163 | 164 | # 165 | # Default duration of the siege. The right hand argument has 166 | # a modifier which specifies the time units, H=hours, M=minutes, 167 | # and S=seconds. If a modifier is not specified, then minutes 168 | # are assumed. 169 | # ex: time = 50M 170 | # 171 | # time = 172 | 173 | # 174 | # Repetitions. The length of siege may be specified in client 175 | # reps rather then a time duration. Instead of specifying a time 176 | # span, you can tell each siege instance to hit the server X number 177 | # of times. So if you chose 'reps = 20' and you've selected 10 178 | # concurrent users, then siege will hit the server 200 times. 179 | # ex: reps = 20 180 | # 181 | # reps = 182 | 183 | # 184 | # Default URLs file, set at configuration time, the default 185 | # file is PREFIX/etc/urls.txt. So if you configured siege 186 | # with --prefix=/usr/local then the urls.txt file is installed 187 | # int /usr/local/etc/urls.txt. Use the "file = " directive to 188 | # configure an alternative URLs file. You may use environment 189 | # variables as shown in the examples below: 190 | # ex: file = /export/home/jdfulmer/MYURLS.txt 191 | # file = $HOME/etc/urls.txt 192 | # file = $URLSFILE 193 | # 194 | # file = 195 | 196 | # 197 | # Default URL, this is a single URL that you want to test. This 198 | # is usually set at the command line with the -u option. When 199 | # used, this option overrides the urls.txt (-f FILE/--file=FILE) 200 | # option. You will HAVE to comment this out for in order to use 201 | # the urls.txt file option. 202 | # 203 | # NOTE: you may do the same thing by passing a URL to siege at 204 | # the command line: siege -c10 -r10 "www.joedog.org/" 205 | # Generally, it's a good idea to wrap a command line URL in quotes 206 | # 207 | # ex: url = https://shemp.whoohoo.com/docs/index.jsp 208 | # 209 | # url = 210 | 211 | # 212 | # Default delay value, see the siege(1) man page. 213 | # This value is used for load testing, it is not used 214 | # for benchmarking. 215 | # ex: delay = 3 216 | # 217 | delay = 1 218 | 219 | # 220 | # Connection timeout value. Set the value in seconds for 221 | # socket connection timeouts. The default value is 30 seconds. 222 | # ex: timeout = 30 223 | # 224 | # timeout = 225 | 226 | # 227 | # Session expiration: This directive allows you to delete all 228 | # cookies after you pass through the URLs. This means siege will 229 | # grab a new session with each run through its URLs. The default 230 | # value is false. 231 | # ex: expire-session = true 232 | # 233 | # expire-session = 234 | 235 | # 236 | # Cookie support: by default siege accepts cookies. This directive 237 | # is available to disable that support. Set cookies to 'false' to 238 | # refuse cookies. Set it to 'true' to accept them. The default value 239 | # is true. 240 | # ex: cookies = false 241 | # 242 | # cookies = 243 | 244 | # 245 | # Failures: This is the number of total connection failures allowed 246 | # before siege aborts. Connection failures (timeouts, socket failures, 247 | # etc.) are combined with 400 and 500 level errors in the final stats, 248 | # but those errors do not count against the abort total. If you set 249 | # this total to 10, then siege will abort after ten socket timeouts, 250 | # but it will NOT abort after ten 404s. This is designed to prevent 251 | # a run-away mess on an unattended siege. The default value is 1024 252 | # ex: failures = 50 253 | # 254 | # failures = 255 | 256 | # 257 | # Internet simulation. If true, siege clients will hit 258 | # the URLs in the urls.txt file randomly, thereby simulating 259 | # internet usage. If false, siege will run through the 260 | # urls.txt file in order from first to last and back again. 261 | # ex: internet = true 262 | # 263 | internet = false 264 | 265 | # 266 | # Default benchmarking value, If true, there is NO delay 267 | # between server requests, siege runs as fast as the web 268 | # server and the network will let it. Set this to false 269 | # for load testing. 270 | # ex: benchmark = true 271 | # 272 | benchmark = false 273 | 274 | # 275 | # Set the siege User-Agent to identify yourself at the 276 | # host, the default is: JoeDog/1.00 [en] (X11; I; Siege #.##) 277 | # But that wreaks of corporate techno speak. Feel free 278 | # to make it more interesting :-) Since Limey is recovering 279 | # from minor surgery as I write this, I'll dedicate the 280 | # example to him... 281 | # 282 | # ex: user-agent = Limey The Bulldog 283 | # 284 | # user-agent = 285 | 286 | # 287 | # Accept-encoding. This option allows you to specify 288 | # acceptable encodings returned by the server. Use this 289 | # directive to turn on compression. By default we accept 290 | # gzip compression. 291 | # 292 | # ex: accept-encoding = * 293 | # accept-encoding = gzip 294 | # accept-encoding = compress;q=0.5;gzip;q=1 295 | accept-encoding = gzip 296 | 297 | # 298 | # URL escaping was added in version 3.0.3. You may use this 299 | # directive to turn off this experimental feature. By default 300 | # this feature is active by default starting with v3.0.3 301 | # 302 | # http://www.joedog.org/jukebox.php?band=the days of new 303 | # becomes: 304 | # http://www.joedog.org/jukebox.php?band=the%20days%20of%20the%20new 305 | # 306 | # ex: url-escaping = false 307 | # 308 | url-escaping = true 309 | 310 | # 311 | # TURN OFF THAT ANNOYING SPINNER! 312 | # Siege spawns a thread and runs a spinner to entertain you 313 | # as it collects and computes its stats. If you don't like 314 | # this feature, you may turn it off here. 315 | # ex: spinner = false 316 | # 317 | spinner = true 318 | 319 | # 320 | # WWW-Authenticate login. When siege hits a webpage 321 | # that requires basic authentication, it will search its 322 | # logins for authentication which matches the specific realm 323 | # requested by the server. If it finds a match, it will send 324 | # that login information. If it fails to match the realm, it 325 | # will send the default login information. (Default is "all"). 326 | # You may configure siege with several logins as long as no 327 | # two realms match. The format for logins is: 328 | # username:password[:realm] where "realm" is optional. 329 | # If you do not supply a realm, then it will default to "all" 330 | # ex: login = jdfulmer:topsecret:Admin 331 | # login = jeff:supersecret 332 | # 333 | # login = 334 | 335 | # 336 | # Login URL. This is the first URL to be hit by every siege 337 | # client. This feature was designed to allow you to login to 338 | # a server and establish a session. It will only be hit once 339 | # so if you need to hit this URL more then once, make sure it 340 | # also appears in your urls.txt file. 341 | # 342 | # ex: login-url = http://eos.haha.com/login.jsp POST name=jeff&pass=foo 343 | # 344 | # Siege versions after 2.69 support multi logins; you can configure 345 | # them with multiple login-url directives. Place each one on a separate 346 | # line. Siege loops through each login then starts again at the beginning 347 | # after it uses the last one. If you have more users than login-urls, then 348 | # siege starts reassigning ones that have already been used. 349 | # 350 | # ex: login-url = http://www.haha.com/login.php?name=homer&pass=whoohoo 351 | # login-url = http://www.haha.com/login.php?name=marge&pass=ohhomie 352 | # login-url = http://www.haha.com/login.php?name=bart&pass=eatMyShorts 353 | # 354 | # login-url = 355 | 356 | # 357 | # FTP login - This directive provides one of two ways 358 | # to login to an ftp server. You may also set credentials 359 | # in RFC-1738 format: ftp://user:pass@host.com/ink.jpg 360 | # 361 | # The format is USER:PASS:HOST separated by colon ':' 362 | # The host field is optional. If you don't set a host, 363 | # then siege will send the same user:pass to every FTP 364 | # server. You may use this directive MULTIPLE times. 365 | # Siege will store each instance in memory and send the 366 | # appropriate credentials at login time depending on the 367 | # hostname in the URL. 368 | # 369 | # ex: ftp-login: jdfulmer:whoohoo:ftp.joedog.org 370 | # ftp-login: jdfulmer:password 371 | # 372 | # ftp-login = 373 | 374 | # 375 | # FTP unique - This directive determines whether siege 376 | # will upload files with the same name (and therefore 377 | # overwrite whatever is on disk) or upload files each with a 378 | # unique name. If true, siege will rewrite the file name with 379 | # a timestamp in its name, i.e., p.jpg => p-3086060432.jpg 380 | # The default value is true. 381 | # 382 | # ex: unique = false 383 | # 384 | unique = true 385 | 386 | # 387 | # ssl-cert 388 | # This optional feature allows you to specify a path to a client 389 | # certificate. It is not neccessary to specify a certificate in 390 | # order to use https. If you don't know why you would want one, 391 | # then you probably don't need this feature. Use openssl to 392 | # generate a certificate and key with the following command: 393 | # $ openssl req -nodes -new -days 365 -newkey rsa:1024 \ 394 | # -keyout key.pem -out cert.pem 395 | # Specify a path to cert.pem as follows: 396 | # ex: ssl-cert = /home/jeff/.certs/cert.pem 397 | # 398 | # ssl-cert = 399 | 400 | # 401 | # ssl-key 402 | # Use this option to specify the key you generated with the command 403 | # above. ex: ssl-key = /home/jeff/.certs/key.pem 404 | # You may actually skip this option and combine both your cert and 405 | # your key in a single file: 406 | # $ cat key.pem > client.pem 407 | # $ cat cert.pem >> client.pem 408 | # Now set the path for ssl-cert: 409 | # ex: ssl-cert = /home/jeff/.certs/client.pem 410 | # (in this scenario, you comment out ssl-key) 411 | # 412 | # ssl-key = 413 | 414 | # 415 | # ssl-timeout 416 | # This option sets a connection timeout for the ssl library 417 | # ex: ssl-timeout = 30 418 | # 419 | # ssl-timeout = 420 | 421 | # 422 | # ssl-ciphers 423 | # You can use this feature to select a specific ssl cipher 424 | # for HTTPs. To view the ones available with your library run 425 | # the following command: openssl ciphers 426 | # ex: ssl-ciphers = EXP-RC4-MD5 427 | # 428 | # ssl-ciphers = 429 | 430 | # 431 | # Proxy-Authenticate. When scout hits a proxy server which 432 | # requires username and password authentication, it will this 433 | # username and password to the server. The format is username, 434 | # password and optional realm each separated by a colon. You 435 | # may enter more than one proxy-login as long as each one has 436 | # a different realm. If you do not enter a realm, then scout 437 | # will send that login information to all proxy challenges. If 438 | # you have more than one proxy-login, then scout will attempt 439 | # to match the login to the realm. 440 | # ex: proxy-login: jeff:secret:corporate 441 | # proxy-login: jeff:whoohoo 442 | # 443 | # proxy-login = 444 | 445 | # 446 | # Redirection support. This option allows to to control 447 | # whether a Location: hint will be followed. Most users 448 | # will want to follow redirection information, but sometimes 449 | # it's desired to just get the Location information. 450 | # 451 | # ex: follow-location = false 452 | # 453 | # follow-location = 454 | 455 | # Zero-length data. siege can be configured to disregard 456 | # results in which zero bytes are read after the headers. 457 | # Alternatively, such results can be counted in the final 458 | # tally of outcomes. 459 | # 460 | # ex: zero-data-ok = false 461 | # 462 | # zero-data-ok = 463 | 464 | # 465 | # end of siegerc 466 | -------------------------------------------------------------------------------- /benchmark/sleep_server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | 5 | http.createServer((req, res) => { 6 | let size = 0; 7 | let data = ''; 8 | req.on('data', chunk => { 9 | size += chunk.length; 10 | data += chunk; 11 | }); 12 | req.on('end', () => { 13 | const timeout = parseInt(req.url.substring(1), 10) || 1; // default 1ms 14 | setTimeout(() => { 15 | const result = { 16 | timeout, 17 | headers: req.headers, 18 | size, 19 | data, 20 | }; 21 | res.socket.setNoDelay(true); 22 | res.end(JSON.stringify(result)); 23 | }, timeout); 24 | }); 25 | }).listen(1984); 26 | 27 | console.log('sleep server start, listen on 1984'); 28 | -------------------------------------------------------------------------------- /benchmark/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # sudo sysctl -w net.inet.ip.portrange.first=12000 4 | # sudo sysctl -w net.inet.tcp.msl=1000 5 | # sudo sysctl -w kern.maxfiles=1000000 kern.maxfilesperproc=1000000 6 | # sudo ulimit -n 100000 7 | 8 | # sysctl -n machdep.cpu.brand_string 9 | 10 | SERVER=127.0.0.1 11 | NUM=500 12 | CONCURRENT=60 13 | maxSockets=50 14 | DELAY=5 15 | POST=/post 16 | 17 | node sleep_server.js & 18 | 19 | sleep_server_pid=$! 20 | 21 | node proxy.js $maxSockets $SERVER & 22 | 23 | sleep 1 24 | 25 | node -v 26 | echo "$maxSockets maxSockets, $CONCURRENT concurrent, $NUM requests per concurrent, ${DELAY}ms delay" 27 | 28 | echo "keep alive" 29 | echo "siege -R siegerc -c $CONCURRENT -r $NUM -b http://localhost:1985${POST}/k/$DELAY" 30 | siege -R siegerc -c $CONCURRENT -r $NUM -b http://localhost:1985${POST}/k/$DELAY 31 | 32 | sleep 5 33 | 34 | echo "normal" 35 | echo "siege -R siegerc -c $CONCURRENT -r $NUM -b http://localhost:1985${POST}/$DELAY" 36 | siege -R siegerc -c $CONCURRENT -r $NUM -b http://localhost:1985${POST}/$DELAY 37 | 38 | sleep 3 39 | 40 | kill $sleep_server_pid 41 | kill % 42 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | module.exports = noop; 2 | module.exports.HttpsAgent = noop; 3 | 4 | // Noop function for browser since native api's don't use agents. 5 | function noop () {} 6 | -------------------------------------------------------------------------------- /example/http_agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const HttpAgent = require('..').HttpAgent; 5 | 6 | const http_agent = new HttpAgent(); 7 | // https://www.google.com/search?q=nodejs&sugexp=chrome,mod=12&sourceid=chrome&ie=UTF-8 8 | 9 | const options = { 10 | host: 'www.taobao.com', 11 | path: '/', 12 | method: 'GET', 13 | port: 80, 14 | agent: http_agent, 15 | }; 16 | 17 | function get() { 18 | const start = Date.now(); 19 | const req = http.get(options, res => { 20 | console.log('STATUS1: %d, %d ms', res.statusCode, Date.now() - start); 21 | console.log('HEADERS1: %j', res.headers); 22 | res.on('data', chunk => { 23 | console.log('BODY1: %d', chunk.length); 24 | }); 25 | res.on('end', () => { 26 | console.log('get end'); 27 | }); 28 | }); 29 | req.on('error', e => { 30 | console.log('problem with request: ' + e.message); 31 | }); 32 | } 33 | 34 | get(); 35 | 36 | setTimeout(() => { 37 | console.log('keep alive sockets:', http_agent); 38 | process.exit(); 39 | }, 300000); 40 | 41 | let count = 0; 42 | setInterval(() => { 43 | const name = http_agent.getName(options); 44 | const sockets = http_agent.sockets[name] || []; 45 | const freeSockets = http_agent.freeSockets[name] || []; 46 | console.log('%ss, %s, sockets: %d, destroyed: %s, free sockets: %d, destroyed: %s', ++count, 47 | name, sockets.length, sockets[0] && sockets[0].destroyed, 48 | freeSockets.length, freeSockets[0] && freeSockets[0].destroyed); 49 | }, 1000); 50 | 51 | setInterval(get, 120000); 52 | -------------------------------------------------------------------------------- /example/https_agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const https = require('https'); 4 | const HttpsAgent = require('..').HttpsAgent; 5 | 6 | const agent = new HttpsAgent(); 7 | // https://www.google.com/search?q=nodejs&sugexp=chrome,mod=12&sourceid=chrome&ie=UTF-8 8 | const options = { 9 | host: 'github.com', 10 | port: 443, 11 | path: '/', 12 | method: 'GET', 13 | agent, 14 | }; 15 | 16 | let start = Date.now(); 17 | const req = https.request(options, res => { 18 | console.log('STATUS1: %d, %d ms', res.statusCode, Date.now() - start); 19 | console.log('HEADERS1: %j', res.headers); 20 | res.setEncoding('utf8'); 21 | res.on('data', chunk => { 22 | console.log('BODY1: %d', chunk.length); 23 | }); 24 | res.on('end', () => { 25 | process.nextTick(() => { 26 | start = Date.now(); 27 | https.get(options, res => { 28 | console.log('STATUS2: %d, %d ms', res.statusCode, Date.now() - start); 29 | console.log('HEADERS2: %j', res.headers); 30 | res.setEncoding('utf8'); 31 | res.on('data', chunk => { 32 | console.log('BODY2: %d', chunk.length); 33 | }); 34 | }); 35 | }); 36 | }); 37 | }); 38 | 39 | req.on('error', e => { 40 | console.log('problem with request: ' + e.message); 41 | }); 42 | req.end(); 43 | 44 | setTimeout(() => { 45 | console.log('keep alive sockets:', agent); 46 | process.exit(); 47 | }, 5000); 48 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | 5 | http.createServer((req, res) => { 6 | req.resume(); 7 | req.on('end', () => { 8 | res.statusCode = 200; 9 | res.end(req.method + ' ' + req.url); 10 | }); 11 | }).listen(8080); 12 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import * as https from 'https'; 3 | import * as net from 'net'; 4 | 5 | interface PlainObject { 6 | [key: string]: any; 7 | } 8 | 9 | declare class _HttpAgent extends http.Agent { 10 | constructor(opts?: AgentKeepAlive.HttpOptions); 11 | readonly statusChanged: boolean; 12 | createConnection(options: net.NetConnectOpts, cb?: Function): net.Socket; 13 | createSocket(req: http.IncomingMessage, options: http.RequestOptions, cb: Function): void; 14 | getCurrentStatus(): AgentKeepAlive.AgentStatus; 15 | } 16 | 17 | interface Constants { 18 | CURRENT_ID: Symbol; 19 | CREATE_ID: Symbol; 20 | INIT_SOCKET: Symbol; 21 | CREATE_HTTPS_CONNECTION: Symbol; 22 | SOCKET_CREATED_TIME: Symbol; 23 | SOCKET_NAME: Symbol; 24 | SOCKET_REQUEST_COUNT: Symbol; 25 | SOCKET_REQUEST_FINISHED_COUNT: Symbol; 26 | } 27 | 28 | /** 29 | * @deprecated instead use `import { HttpAgent } from 'agentkeepalive'; or `const HttpAgent = require('agentkeepalive').HttpAgent;` 30 | */ 31 | declare class AgentKeepAlive extends _HttpAgent {} 32 | 33 | declare namespace AgentKeepAlive { 34 | export interface AgentStatus { 35 | createSocketCount: number; 36 | createSocketErrorCount: number; 37 | closeSocketCount: number; 38 | errorSocketCount: number; 39 | timeoutSocketCount: number; 40 | requestCount: number; 41 | freeSockets: PlainObject; 42 | sockets: PlainObject; 43 | requests: PlainObject; 44 | } 45 | 46 | interface CommonHttpOption { 47 | keepAlive?: boolean | undefined; 48 | freeSocketTimeout?: number | undefined; 49 | freeSocketKeepAliveTimeout?: number | undefined; 50 | timeout?: number | undefined; 51 | socketActiveTTL?: number | undefined; 52 | } 53 | 54 | export interface HttpOptions extends http.AgentOptions, CommonHttpOption { } 55 | export interface HttpsOptions extends https.AgentOptions, CommonHttpOption { } 56 | 57 | export class HttpAgent extends _HttpAgent {} 58 | export class HttpsAgent extends https.Agent { 59 | constructor(opts?: HttpsOptions); 60 | readonly statusChanged: boolean; 61 | createConnection(options: net.NetConnectOpts, cb?: Function): net.Socket; 62 | createSocket(req: http.IncomingMessage, options: http.RequestOptions, cb: Function): void; 63 | getCurrentStatus(): AgentStatus; 64 | } 65 | 66 | export const constants: Constants; 67 | } 68 | 69 | export = AgentKeepAlive; 70 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const HttpAgent = require('./lib/agent'); 4 | module.exports = HttpAgent; 5 | module.exports.HttpAgent = HttpAgent; 6 | module.exports.HttpsAgent = require('./lib/https_agent'); 7 | module.exports.constants = require('./lib/constants'); 8 | -------------------------------------------------------------------------------- /lib/agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const OriginalAgent = require('http').Agent; 4 | const ms = require('humanize-ms'); 5 | const debug = require('util').debuglog('agentkeepalive'); 6 | const { 7 | INIT_SOCKET, 8 | CURRENT_ID, 9 | CREATE_ID, 10 | SOCKET_CREATED_TIME, 11 | SOCKET_NAME, 12 | SOCKET_REQUEST_COUNT, 13 | SOCKET_REQUEST_FINISHED_COUNT, 14 | } = require('./constants'); 15 | 16 | // OriginalAgent come from 17 | // - https://github.com/nodejs/node/blob/v8.12.0/lib/_http_agent.js 18 | // - https://github.com/nodejs/node/blob/v10.12.0/lib/_http_agent.js 19 | 20 | // node <= 10 21 | let defaultTimeoutListenerCount = 1; 22 | const majorVersion = parseInt(process.version.split('.', 1)[0].substring(1)); 23 | if (majorVersion >= 11 && majorVersion <= 12) { 24 | defaultTimeoutListenerCount = 2; 25 | } else if (majorVersion >= 13) { 26 | defaultTimeoutListenerCount = 3; 27 | } 28 | 29 | function deprecate(message) { 30 | console.log('[agentkeepalive:deprecated] %s', message); 31 | } 32 | 33 | class Agent extends OriginalAgent { 34 | constructor(options) { 35 | options = options || {}; 36 | options.keepAlive = options.keepAlive !== false; 37 | // default is keep-alive and 4s free socket timeout 38 | // see https://medium.com/ssense-tech/reduce-networking-errors-in-nodejs-23b4eb9f2d83 39 | if (options.freeSocketTimeout === undefined) { 40 | options.freeSocketTimeout = 4000; 41 | } 42 | // Legacy API: keepAliveTimeout should be rename to `freeSocketTimeout` 43 | if (options.keepAliveTimeout) { 44 | deprecate('options.keepAliveTimeout is deprecated, please use options.freeSocketTimeout instead'); 45 | options.freeSocketTimeout = options.keepAliveTimeout; 46 | delete options.keepAliveTimeout; 47 | } 48 | // Legacy API: freeSocketKeepAliveTimeout should be rename to `freeSocketTimeout` 49 | if (options.freeSocketKeepAliveTimeout) { 50 | deprecate('options.freeSocketKeepAliveTimeout is deprecated, please use options.freeSocketTimeout instead'); 51 | options.freeSocketTimeout = options.freeSocketKeepAliveTimeout; 52 | delete options.freeSocketKeepAliveTimeout; 53 | } 54 | 55 | // Sets the socket to timeout after timeout milliseconds of inactivity on the socket. 56 | // By default is double free socket timeout. 57 | if (options.timeout === undefined) { 58 | // make sure socket default inactivity timeout >= 8s 59 | options.timeout = Math.max(options.freeSocketTimeout * 2, 8000); 60 | } 61 | 62 | // support humanize format 63 | options.timeout = ms(options.timeout); 64 | options.freeSocketTimeout = ms(options.freeSocketTimeout); 65 | options.socketActiveTTL = options.socketActiveTTL ? ms(options.socketActiveTTL) : 0; 66 | 67 | super(options); 68 | 69 | this[CURRENT_ID] = 0; 70 | 71 | // create socket success counter 72 | this.createSocketCount = 0; 73 | this.createSocketCountLastCheck = 0; 74 | 75 | this.createSocketErrorCount = 0; 76 | this.createSocketErrorCountLastCheck = 0; 77 | 78 | this.closeSocketCount = 0; 79 | this.closeSocketCountLastCheck = 0; 80 | 81 | // socket error event count 82 | this.errorSocketCount = 0; 83 | this.errorSocketCountLastCheck = 0; 84 | 85 | // request finished counter 86 | this.requestCount = 0; 87 | this.requestCountLastCheck = 0; 88 | 89 | // including free socket timeout counter 90 | this.timeoutSocketCount = 0; 91 | this.timeoutSocketCountLastCheck = 0; 92 | 93 | this.on('free', socket => { 94 | // https://github.com/nodejs/node/pull/32000 95 | // Node.js native agent will check socket timeout eqs agent.options.timeout. 96 | // Use the ttl or freeSocketTimeout to overwrite. 97 | const timeout = this.calcSocketTimeout(socket); 98 | if (timeout > 0 && socket.timeout !== timeout) { 99 | socket.setTimeout(timeout); 100 | } 101 | }); 102 | } 103 | 104 | get freeSocketKeepAliveTimeout() { 105 | deprecate('agent.freeSocketKeepAliveTimeout is deprecated, please use agent.options.freeSocketTimeout instead'); 106 | return this.options.freeSocketTimeout; 107 | } 108 | 109 | get timeout() { 110 | deprecate('agent.timeout is deprecated, please use agent.options.timeout instead'); 111 | return this.options.timeout; 112 | } 113 | 114 | get socketActiveTTL() { 115 | deprecate('agent.socketActiveTTL is deprecated, please use agent.options.socketActiveTTL instead'); 116 | return this.options.socketActiveTTL; 117 | } 118 | 119 | calcSocketTimeout(socket) { 120 | /** 121 | * return <= 0: should free socket 122 | * return > 0: should update socket timeout 123 | * return undefined: not find custom timeout 124 | */ 125 | let freeSocketTimeout = this.options.freeSocketTimeout; 126 | const socketActiveTTL = this.options.socketActiveTTL; 127 | if (socketActiveTTL) { 128 | // check socketActiveTTL 129 | const aliveTime = Date.now() - socket[SOCKET_CREATED_TIME]; 130 | const diff = socketActiveTTL - aliveTime; 131 | if (diff <= 0) { 132 | return diff; 133 | } 134 | if (freeSocketTimeout && diff < freeSocketTimeout) { 135 | freeSocketTimeout = diff; 136 | } 137 | } 138 | // set freeSocketTimeout 139 | if (freeSocketTimeout) { 140 | // set free keepalive timer 141 | // try to use socket custom freeSocketTimeout first, support headers['keep-alive'] 142 | // https://github.com/node-modules/urllib/blob/b76053020923f4d99a1c93cf2e16e0c5ba10bacf/lib/urllib.js#L498 143 | const customFreeSocketTimeout = socket.freeSocketTimeout || socket.freeSocketKeepAliveTimeout; 144 | return customFreeSocketTimeout || freeSocketTimeout; 145 | } 146 | } 147 | 148 | keepSocketAlive(socket) { 149 | const result = super.keepSocketAlive(socket); 150 | // should not keepAlive, do nothing 151 | if (!result) return result; 152 | 153 | const customTimeout = this.calcSocketTimeout(socket); 154 | if (typeof customTimeout === 'undefined') { 155 | return true; 156 | } 157 | if (customTimeout <= 0) { 158 | debug('%s(requests: %s, finished: %s) free but need to destroy by TTL, request count %s, diff is %s', 159 | socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT], customTimeout); 160 | return false; 161 | } 162 | if (socket.timeout !== customTimeout) { 163 | socket.setTimeout(customTimeout); 164 | } 165 | return true; 166 | } 167 | 168 | // only call on addRequest 169 | reuseSocket(...args) { 170 | // reuseSocket(socket, req) 171 | super.reuseSocket(...args); 172 | const socket = args[0]; 173 | const req = args[1]; 174 | req.reusedSocket = true; 175 | const agentTimeout = this.options.timeout; 176 | if (getSocketTimeout(socket) !== agentTimeout) { 177 | // reset timeout before use 178 | socket.setTimeout(agentTimeout); 179 | debug('%s reset timeout to %sms', socket[SOCKET_NAME], agentTimeout); 180 | } 181 | socket[SOCKET_REQUEST_COUNT]++; 182 | debug('%s(requests: %s, finished: %s) reuse on addRequest, timeout %sms', 183 | socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT], 184 | getSocketTimeout(socket)); 185 | } 186 | 187 | [CREATE_ID]() { 188 | const id = this[CURRENT_ID]++; 189 | if (this[CURRENT_ID] === Number.MAX_SAFE_INTEGER) this[CURRENT_ID] = 0; 190 | return id; 191 | } 192 | 193 | [INIT_SOCKET](socket, options) { 194 | // bugfix here. 195 | // https on node 8, 10 won't set agent.options.timeout by default 196 | // TODO: need to fix on node itself 197 | if (options.timeout) { 198 | const timeout = getSocketTimeout(socket); 199 | if (!timeout) { 200 | socket.setTimeout(options.timeout); 201 | } 202 | } 203 | 204 | if (this.options.keepAlive) { 205 | // Disable Nagle's algorithm: http://blog.caustik.com/2012/04/08/scaling-node-js-to-100k-concurrent-connections/ 206 | // https://fengmk2.com/benchmark/nagle-algorithm-delayed-ack-mock.html 207 | socket.setNoDelay(true); 208 | } 209 | this.createSocketCount++; 210 | if (this.options.socketActiveTTL) { 211 | socket[SOCKET_CREATED_TIME] = Date.now(); 212 | } 213 | // don't show the hole '-----BEGIN CERTIFICATE----' key string 214 | socket[SOCKET_NAME] = `sock[${this[CREATE_ID]()}#${options._agentKey}]`.split('-----BEGIN', 1)[0]; 215 | socket[SOCKET_REQUEST_COUNT] = 1; 216 | socket[SOCKET_REQUEST_FINISHED_COUNT] = 0; 217 | installListeners(this, socket, options); 218 | } 219 | 220 | createConnection(options, oncreate) { 221 | let called = false; 222 | const onNewCreate = (err, socket) => { 223 | if (called) return; 224 | called = true; 225 | 226 | if (err) { 227 | this.createSocketErrorCount++; 228 | return oncreate(err); 229 | } 230 | this[INIT_SOCKET](socket, options); 231 | oncreate(err, socket); 232 | }; 233 | 234 | const newSocket = super.createConnection(options, onNewCreate); 235 | if (newSocket) onNewCreate(null, newSocket); 236 | return newSocket; 237 | } 238 | 239 | get statusChanged() { 240 | const changed = this.createSocketCount !== this.createSocketCountLastCheck || 241 | this.createSocketErrorCount !== this.createSocketErrorCountLastCheck || 242 | this.closeSocketCount !== this.closeSocketCountLastCheck || 243 | this.errorSocketCount !== this.errorSocketCountLastCheck || 244 | this.timeoutSocketCount !== this.timeoutSocketCountLastCheck || 245 | this.requestCount !== this.requestCountLastCheck; 246 | if (changed) { 247 | this.createSocketCountLastCheck = this.createSocketCount; 248 | this.createSocketErrorCountLastCheck = this.createSocketErrorCount; 249 | this.closeSocketCountLastCheck = this.closeSocketCount; 250 | this.errorSocketCountLastCheck = this.errorSocketCount; 251 | this.timeoutSocketCountLastCheck = this.timeoutSocketCount; 252 | this.requestCountLastCheck = this.requestCount; 253 | } 254 | return changed; 255 | } 256 | 257 | getCurrentStatus() { 258 | return { 259 | createSocketCount: this.createSocketCount, 260 | createSocketErrorCount: this.createSocketErrorCount, 261 | closeSocketCount: this.closeSocketCount, 262 | errorSocketCount: this.errorSocketCount, 263 | timeoutSocketCount: this.timeoutSocketCount, 264 | requestCount: this.requestCount, 265 | freeSockets: inspect(this.freeSockets), 266 | sockets: inspect(this.sockets), 267 | requests: inspect(this.requests), 268 | }; 269 | } 270 | } 271 | 272 | // node 8 don't has timeout attribute on socket 273 | // https://github.com/nodejs/node/pull/21204/files#diff-e6ef024c3775d787c38487a6309e491dR408 274 | function getSocketTimeout(socket) { 275 | return socket.timeout || socket._idleTimeout; 276 | } 277 | 278 | function installListeners(agent, socket, options) { 279 | debug('%s create, timeout %sms', socket[SOCKET_NAME], getSocketTimeout(socket)); 280 | 281 | // listener socket events: close, timeout, error, free 282 | function onFree() { 283 | // create and socket.emit('free') logic 284 | // https://github.com/nodejs/node/blob/master/lib/_http_agent.js#L311 285 | // no req on the socket, it should be the new socket 286 | if (!socket._httpMessage && socket[SOCKET_REQUEST_COUNT] === 1) return; 287 | 288 | socket[SOCKET_REQUEST_FINISHED_COUNT]++; 289 | agent.requestCount++; 290 | debug('%s(requests: %s, finished: %s) free', 291 | socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT]); 292 | 293 | // should reuse on pedding requests? 294 | const name = agent.getName(options); 295 | if (socket.writable && agent.requests[name] && agent.requests[name].length) { 296 | // will be reuse on agent free listener 297 | socket[SOCKET_REQUEST_COUNT]++; 298 | debug('%s(requests: %s, finished: %s) will be reuse on agent free event', 299 | socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT]); 300 | } 301 | } 302 | socket.on('free', onFree); 303 | 304 | function onClose(isError) { 305 | debug('%s(requests: %s, finished: %s) close, isError: %s', 306 | socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT], isError); 307 | agent.closeSocketCount++; 308 | } 309 | socket.on('close', onClose); 310 | 311 | // start socket timeout handler 312 | function onTimeout() { 313 | // onTimeout and emitRequestTimeout(_http_client.js) 314 | // https://github.com/nodejs/node/blob/v12.x/lib/_http_client.js#L711 315 | const listenerCount = socket.listeners('timeout').length; 316 | // node <= 10, default listenerCount is 1, onTimeout 317 | // 11 < node <= 12, default listenerCount is 2, onTimeout and emitRequestTimeout 318 | // node >= 13, default listenerCount is 3, onTimeout, 319 | // onTimeout(https://github.com/nodejs/node/pull/32000/files#diff-5f7fb0850412c6be189faeddea6c5359R333) 320 | // and emitRequestTimeout 321 | const timeout = getSocketTimeout(socket); 322 | const req = socket._httpMessage; 323 | const reqTimeoutListenerCount = req && req.listeners('timeout').length || 0; 324 | debug('%s(requests: %s, finished: %s) timeout after %sms, listeners %s, defaultTimeoutListenerCount %s, hasHttpRequest %s, HttpRequest timeoutListenerCount %s', 325 | socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT], 326 | timeout, listenerCount, defaultTimeoutListenerCount, !!req, reqTimeoutListenerCount); 327 | if (debug.enabled) { 328 | debug('timeout listeners: %s', socket.listeners('timeout').map(f => f.name).join(', ')); 329 | } 330 | agent.timeoutSocketCount++; 331 | const name = agent.getName(options); 332 | if (agent.freeSockets[name] && agent.freeSockets[name].indexOf(socket) !== -1) { 333 | // free socket timeout, destroy quietly 334 | socket.destroy(); 335 | // Remove it from freeSockets list immediately to prevent new requests 336 | // from being sent through this socket. 337 | agent.removeSocket(socket, options); 338 | debug('%s is free, destroy quietly', socket[SOCKET_NAME]); 339 | } else { 340 | // if there is no any request socket timeout handler, 341 | // agent need to handle socket timeout itself. 342 | // 343 | // custom request socket timeout handle logic must follow these rules: 344 | // 1. Destroy socket first 345 | // 2. Must emit socket 'agentRemove' event tell agent remove socket 346 | // from freeSockets list immediately. 347 | // Otherise you may be get 'socket hang up' error when reuse 348 | // free socket and timeout happen in the same time. 349 | if (reqTimeoutListenerCount === 0) { 350 | const error = new Error('Socket timeout'); 351 | error.code = 'ERR_SOCKET_TIMEOUT'; 352 | error.timeout = timeout; 353 | // must manually call socket.end() or socket.destroy() to end the connection. 354 | // https://nodejs.org/dist/latest-v10.x/docs/api/net.html#net_socket_settimeout_timeout_callback 355 | socket.destroy(error); 356 | agent.removeSocket(socket, options); 357 | debug('%s destroy with timeout error', socket[SOCKET_NAME]); 358 | } 359 | } 360 | } 361 | socket.on('timeout', onTimeout); 362 | 363 | function onError(err) { 364 | const listenerCount = socket.listeners('error').length; 365 | debug('%s(requests: %s, finished: %s) error: %s, listenerCount: %s', 366 | socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT], 367 | err, listenerCount); 368 | agent.errorSocketCount++; 369 | if (listenerCount === 1) { 370 | // if socket don't contain error event handler, don't catch it, emit it again 371 | debug('%s emit uncaught error event', socket[SOCKET_NAME]); 372 | socket.removeListener('error', onError); 373 | socket.emit('error', err); 374 | } 375 | } 376 | socket.on('error', onError); 377 | 378 | function onRemove() { 379 | debug('%s(requests: %s, finished: %s) agentRemove', 380 | socket[SOCKET_NAME], 381 | socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT]); 382 | // We need this function for cases like HTTP 'upgrade' 383 | // (defined by WebSockets) where we need to remove a socket from the 384 | // pool because it'll be locked up indefinitely 385 | socket.removeListener('close', onClose); 386 | socket.removeListener('error', onError); 387 | socket.removeListener('free', onFree); 388 | socket.removeListener('timeout', onTimeout); 389 | socket.removeListener('agentRemove', onRemove); 390 | } 391 | socket.on('agentRemove', onRemove); 392 | } 393 | 394 | module.exports = Agent; 395 | 396 | function inspect(obj) { 397 | const res = {}; 398 | for (const key in obj) { 399 | res[key] = obj[key].length; 400 | } 401 | return res; 402 | } 403 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | // agent 5 | CURRENT_ID: Symbol('agentkeepalive#currentId'), 6 | CREATE_ID: Symbol('agentkeepalive#createId'), 7 | INIT_SOCKET: Symbol('agentkeepalive#initSocket'), 8 | CREATE_HTTPS_CONNECTION: Symbol('agentkeepalive#createHttpsConnection'), 9 | // socket 10 | SOCKET_CREATED_TIME: Symbol('agentkeepalive#socketCreatedTime'), 11 | SOCKET_NAME: Symbol('agentkeepalive#socketName'), 12 | SOCKET_REQUEST_COUNT: Symbol('agentkeepalive#socketRequestCount'), 13 | SOCKET_REQUEST_FINISHED_COUNT: Symbol('agentkeepalive#socketRequestFinishedCount'), 14 | }; 15 | -------------------------------------------------------------------------------- /lib/https_agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const OriginalHttpsAgent = require('https').Agent; 4 | const HttpAgent = require('./agent'); 5 | const { 6 | INIT_SOCKET, 7 | CREATE_HTTPS_CONNECTION, 8 | } = require('./constants'); 9 | 10 | class HttpsAgent extends HttpAgent { 11 | constructor(options) { 12 | super(options); 13 | 14 | this.defaultPort = 443; 15 | this.protocol = 'https:'; 16 | this.maxCachedSessions = this.options.maxCachedSessions; 17 | /* istanbul ignore next */ 18 | if (this.maxCachedSessions === undefined) { 19 | this.maxCachedSessions = 100; 20 | } 21 | 22 | this._sessionCache = { 23 | map: {}, 24 | list: [], 25 | }; 26 | } 27 | 28 | createConnection(options, oncreate) { 29 | const socket = this[CREATE_HTTPS_CONNECTION](options, oncreate); 30 | this[INIT_SOCKET](socket, options); 31 | return socket; 32 | } 33 | } 34 | 35 | // https://github.com/nodejs/node/blob/master/lib/https.js#L89 36 | HttpsAgent.prototype[CREATE_HTTPS_CONNECTION] = OriginalHttpsAgent.prototype.createConnection; 37 | 38 | [ 39 | 'getName', 40 | '_getSession', 41 | '_cacheSession', 42 | // https://github.com/nodejs/node/pull/4982 43 | '_evictSession', 44 | ].forEach(function(method) { 45 | /* istanbul ignore next */ 46 | if (typeof OriginalHttpsAgent.prototype[method] === 'function') { 47 | HttpsAgent.prototype[method] = OriginalHttpsAgent.prototype[method]; 48 | } 49 | }); 50 | 51 | module.exports = HttpsAgent; 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agentkeepalive", 3 | "version": "4.6.0", 4 | "description": "Missing keepalive http.Agent", 5 | "main": "index.js", 6 | "browser": "browser.js", 7 | "files": [ 8 | "index.js", 9 | "index.d.ts", 10 | "browser.js", 11 | "lib" 12 | ], 13 | "scripts": { 14 | "contributor": "git-contributor", 15 | "test": "npm run lint && egg-bin test --full-trace", 16 | "test-local": "egg-bin test --full-trace", 17 | "cov": "cross-env NODE_DEBUG=agentkeepalive egg-bin cov --full-trace", 18 | "ci": "npm run lint && npm run cov", 19 | "lint": "eslint lib test index.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/node-modules/agentkeepalive.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/node-modules/agentkeepalive/issues" 27 | }, 28 | "keywords": [ 29 | "http", 30 | "https", 31 | "agent", 32 | "keepalive", 33 | "agentkeepalive", 34 | "HttpAgent", 35 | "HttpsAgent" 36 | ], 37 | "dependencies": { 38 | "humanize-ms": "^1.2.1" 39 | }, 40 | "devDependencies": { 41 | "coffee": "^5.3.0", 42 | "cross-env": "^6.0.3", 43 | "egg-bin": "^4.9.0", 44 | "eslint": "^5.7.0", 45 | "eslint-config-egg": "^7.1.0", 46 | "git-contributor": "^2.0.0", 47 | "mm": "^2.4.1", 48 | "pedding": "^1.1.0", 49 | "typescript": "^3.8.3" 50 | }, 51 | "engines": { 52 | "node": ">= 8.0.0" 53 | }, 54 | "author": "fengmk2 (https://github.com/fengmk2)", 55 | "license": "MIT" 56 | } 57 | -------------------------------------------------------------------------------- /test/fixtures/agenttest-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDyzCCArOgAwIBAgIUWHgqUZfGb/UAhDwy/TTdYkE3CiwwDQYJKoZIhvcNAQEL 3 | BQAwYTELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJ 4 | U3RvY2tob2xtMQswCQYDVQQDDAJjYTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs 5 | ZS5jb20wHhcNMjQxMjI4MTM0NzE1WhcNMjcwOTIzMTM0NzE1WjBSMQswCQYDVQQG 6 | EwJTRTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xGzAZ 7 | BgNVBAMMEmFnZW50a2VlcGFsaXZlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP 8 | ADCCAQoCggEBAKyIpvObe/sNLw0k8KAQiKXhV233wPQDnocwP5xmoYi7Au84U4Dx 9 | esYk/aursc2S/jkiM1XCO37/FEe3IYffSH4D4HuBo+6jPzBSbbDFqPYNM+om52ad 10 | FjxWhN/v4Kwrc5a3nVilWULgsFqE5LiXp5Vuoi7HyEuGya23wM825QddgxizNRRr 11 | FT+Y7hDbGFdW/ZVTq3cpXm9E8sFkPMqiQUiZSt5XCPuh7AqN2Mt4Ghuebu/GhC7T 12 | CebAe+24ylIlY8bpg5m5bpyhZR7vOjVS6bcCR8MxONimWwlN50XYHgB5PhMEkCM3 13 | jsB0xrxJSAHQraPOXeIwfxORxdW1qs0fKa0CAwEAAaOBiTCBhjAsBgNVHREEJTAj 14 | hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABgglsb2NhbGhvc3QwCQYDVR0TBAIwADAL 15 | BgNVHQ8EBAMCBeAwHQYDVR0OBBYEFPp94MOUHtJpzS+9rDjBtyrxEPqgMB8GA1Ud 16 | IwQYMBaAFB7Dfbd+Q1uFOsGWENIzhFSA6e8TMA0GCSqGSIb3DQEBCwUAA4IBAQA4 17 | DQxg2XCtwDkg4Blw2vl/E02PeY4vVXtkYpY3TNi4eE9YAqxXtyF98+Vpgbhyh5zv 18 | I+1h5h/UQbbMp4kSRXTx4BdE7ptSQdcTBOtU3RildOCdXQNFVE+Xc8LVIABL8tNK 19 | 0Fmip2QJEgjUFKgziLs5/XLRGL1JL5+sIrtK49XlwSazLDAdDDQdfC4f0YPnoQQk 20 | TrTBMoe0z98iMcYkAI6ffRxJTZvTmFtX0eYLSgIj/KmNCRlSGouLpjmV3yLu5hI1 21 | LU4KOteqAiZyuxNDntFWD8tkJrX8qLUn9WHsZ0ud+9HvRQ8A5YMk6YPJSMf1KvF9 22 | TjiKvg3asb2CwDe3y0fG 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /test/fixtures/agenttest-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsiKbzm3v7DS8N 3 | JPCgEIil4Vdt98D0A56HMD+cZqGIuwLvOFOA8XrGJP2rq7HNkv45IjNVwjt+/xRH 4 | tyGH30h+A+B7gaPuoz8wUm2wxaj2DTPqJudmnRY8VoTf7+CsK3OWt51YpVlC4LBa 5 | hOS4l6eVbqIux8hLhsmtt8DPNuUHXYMYszUUaxU/mO4Q2xhXVv2VU6t3KV5vRPLB 6 | ZDzKokFImUreVwj7oewKjdjLeBobnm7vxoQu0wnmwHvtuMpSJWPG6YOZuW6coWUe 7 | 7zo1Uum3AkfDMTjYplsJTedF2B4AeT4TBJAjN47AdMa8SUgB0K2jzl3iMH8TkcXV 8 | tarNHymtAgMBAAECggEAHy3s2d5R5/socxwnXg3O3AdemPFqjc5voieufzGu5HpD 9 | W/7WXFmHYfKCYzk6fxee2K1dEJTy0o7/V7x5E0hfHeLOeRNjEDexLibfStBVqe2V 10 | 4a1ZKRqfT5UlGyEK/aI2l9ij10a+XE8ln54fhVpmvyMIrSKOiFOZ88pezjOx7QQF 11 | Y+LnKVb6b4yTPr7T9G7el7rzUtzf1rgvTib4+Dfe1ZwGHXdXSdGGqskOxOEre1zA 12 | RdCddjCYOBPHQ1fU/NN54/JidtbHRrYqnJPd7WjXLqXbIAifSCWZsgGTRoJt9HWm 13 | 2UG0dWA/+ab9dMEuS24YsqueZDpPZ6y7YBMV7Fo0wwKBgQDnOQx/MfFMPEzXC5QI 14 | 8M6w2MDpnSOCCE07ykny9F5who9ilPTr7Me5Cyx+uftC/7Ax5KmIPatWxOTtEB4q 15 | +RVW/PeRD8oq2+VHmC8XLlq7wlllbsi6HYo89pcUpHcGFI5wOFyBRBCSDVBjFXOO 16 | XaXtpK06NHU5Osv8WmHA3NHc/wKBgQC/BaI2pvSFdvXI5W/PtKHOH4xpeJO/8Ziu 17 | zFd9RCvHNxwfyLKrAHXqaCqDR3OPPN4Ao2JXaTy+f4b97tGHX4mIn8oaBmaUCDTy 18 | xCNk1C29daBMBRBqveaXPCfPcLgNYvMx6btgoSqGQBtMNyro48hw7uD2YF3DMZ56 19 | iUrFR9R9UwKBgCVsP1CK1cH/9HPNpOz5hIps2nQ1AZ03GMD3kZZn5K0TqjtLXoSc 20 | swqI+2+bTEZgubSpjKLHUGbfwSl5NVjBLaoBkVQCGTdslaRLxjtbPkYrQ2q+TnCI 21 | /Wm2g2dM4xKx2wfgp2AokwIAc6VFwIknMQbQY1ULTnmvwXobarzbQIAjAoGBAJcC 22 | +yQ9hJ1mWBRD3crUP/5Vzokq+5MIie3WOWwcUoehN6ig3y50pMN1Kfayq9aXBeJ3 23 | R61W0uC+rJdfD2H30yChQgKlAL8SZdlt2ZVCcA6RSPIQJtRb4Em7ErXZpIdgrGXP 24 | I4TWpGmRkIMGMfP+71zoXAwqTrWbKnAwzV181a1lAoGBAJd7BCyoK6+5BiUzkd0d 25 | asTVaV/BYDWdVrFFr78vjVoscqT66+yBmX3xo65wMHkw0yaRNWfKqE/1yseczQgi 26 | Q3IZCM9YIs+JIGP0mzNzK0apZTiEUwGEDnNvvK41hEMPfLypTBUyAM/mz0E9SyS8 27 | 2MTb4gwiIdfW8sgapxzGER7l 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/fixtures/ca.cnf: -------------------------------------------------------------------------------- 1 | [ ca ] 2 | default_ca = CA_default 3 | 4 | [ CA_default ] 5 | serial = ca-serial 6 | crl = ca-crl.pem 7 | database = ca-database.txt 8 | name_opt = CA_default 9 | cert_opt = CA_default 10 | default_crl_days = 9999 11 | default_md = md5 12 | 13 | [ req ] 14 | default_bits = 2048 15 | days = 9999 16 | distinguished_name = req_distinguished_name 17 | attributes = req_attributes 18 | prompt = no 19 | 20 | [ req_distinguished_name ] 21 | C = SE 22 | ST = Stockholm 23 | L = Stockholm 24 | CN = ca 25 | emailAddress = ca@example.com 26 | 27 | [ req_attributes ] 28 | challengePassword = test -------------------------------------------------------------------------------- /test/fixtures/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBWx17nSe1zx8/ 3 | OTST6T5TtYn+lg9v4FCE5+qvukegqIezG79O6J4fjHJr6DagkMlYjGedJOuZd5NS 4 | jZOgb6Zz8ZRVnmxGFhfndu5PnFQTbSrEDZKQEB9/GDLvPhmrbyMtKvj+8m2s9rUH 5 | jCA+o76476o25LlA8GTP1HqZFj3oK0nPsCuuDrEy7aZYTdrDKI+FU7xsZHJZrrEQ 6 | PrBb04VtdlFnjOBJ1UeAWu9uypFJMaLzophgjIQZY6XFXWKYEV55MJgudmurC+sI 7 | 3lTpgnNNHuvjpKZ5xZ6pLP0O4pWs7GqWBK11fM0bTYxUcf7Xlnv0yuE7K3ffRYST 8 | 2GCbbxX1AgMBAAECggEAB/X8UHiBR/MhELif79t7FVw7APaxtQP+nLI/XE1zOuvY 9 | oA9RtAO2biSzGbrdjbfYXZZ0tg7s/YxJ59MfbTKCBaUNKLp7yVjwhZ2rPmJ4p07n 10 | 1o5oGeYoCp+qI5jvDdSaFXdLxpQ5rGG0XpaueMj27P6Lh6eo8ig6DMDtUuJypkPl 11 | Kc0gVX+eCZu7f3O+wI3WLykDO7csn/GQkS4Mcy+hRKVch4gGAU5EAzuLLU+vRPBS 12 | OLmj1X5O3epZchf+hzwB4/Sn1826Dw5Lni63NNOdsTCTLi2Rb2JUsutkzxweYtbw 13 | y8EXQuaruMWoERNt2qy2P85aTmwXqfHJe45BA8UiEwKBgQD5cy4pcOZLRMzr1AGI 14 | ybjrGR3DsDpFOez39LpUZNGFRVnGU7v2JUV7allx0HLtSJYJQR1BoGIKZbcgT2Ho 15 | CLurAfAsBAHzvRfHZUc32HLru/1XYe2hNllG8GkybMqYQiFIOuPEdKOIIlwFJYpY 16 | 9mp4VNrfwuAaxTBQcDcHsQO1ewKBgQDGbt33oStwsi3xqziYazXyB8bko06Q+lH6 17 | k4DdckVPGDealKG4drdi+YAK/tB+YeShC7TiZx9GRKQDyED2eQucc+henLSsdCYJ 18 | RoZP9A4uORc4/sO0PE0mRx/ww06BVa5uaGv2xTeNt9ojRvmV/NwKyTx5NDZoPwM9 19 | yKupukGvTwKBgQCP05xrIoB2J54r31pfH0gyrZGe0g5W+dYrBX0lydeQivL/SipW 20 | +xh7107pM7IAJFj1zwhqXWoJ6qnSxFKEMfza1cw/5LOncIC0ZC3TAkcIqqSg7ILR 21 | 7/87ysQs4dNSRNbhyaqoDER56q25/fIt5y5uYSat27PRW57G0ly4X5Tu3wKBgGZJ 22 | A9X/mBrRXely7wtySC5oX9e/bmJBBjz6B7UekCeDPjZKY0pot4MnRR9l1icvYuC4 23 | 3hbOPUrFWx4v/XyPTLLq9F7AvEkg3fJuDhHspdqhxxy0BkFDzCjtBMPgiPkWJ4H2 24 | BaEAa/B7UtBYZ5Mu8mYE8U0w4tK9mHgiloo43l71AoGBAKJcF1MuHvddV7SzyK7j 25 | Hf7QdPzsLrPrqH6k9L1yVP1RsouHEgoHh63ot3cZBJEtDodjw9ISoiT4Z1SeGCva 26 | BSXYYjKlwo6ik6kFdy237VJu7XorEmpzhK+K2hoG7azyVJG3imsKCWHW7N75e+Id 27 | A+8fxkdCTHA8dB0pk4Rdth1l 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/fixtures/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDczCCAlugAwIBAgIUYxxarpaqtKbdGhWDXUVEv9dGkvIwDQYJKoZIhvcNAQEL 3 | BQAwYTELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0b2NraG9sbTESMBAGA1UEBwwJ 4 | U3RvY2tob2xtMQswCQYDVQQDDAJjYTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs 5 | ZS5jb20wIBcNMjQxMjI4MTM0NzE1WhgPMjA1MjA1MTQxMzQ3MTVaMGExCzAJBgNV 6 | BAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcMCVN0b2NraG9sbTEL 7 | MAkGA1UEAwwCY2ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMIIBIjAN 8 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwVsde50ntc8fPzk0k+k+U7WJ/pYP 9 | b+BQhOfqr7pHoKiHsxu/TuieH4xya+g2oJDJWIxnnSTrmXeTUo2ToG+mc/GUVZ5s 10 | RhYX53buT5xUE20qxA2SkBAffxgy7z4Zq28jLSr4/vJtrPa1B4wgPqO+uO+qNuS5 11 | QPBkz9R6mRY96CtJz7Arrg6xMu2mWE3awyiPhVO8bGRyWa6xED6wW9OFbXZRZ4zg 12 | SdVHgFrvbsqRSTGi86KYYIyEGWOlxV1imBFeeTCYLnZrqwvrCN5U6YJzTR7r46Sm 13 | ecWeqSz9DuKVrOxqlgStdXzNG02MVHH+15Z79MrhOyt330WEk9hgm28V9QIDAQAB 14 | oyEwHzAdBgNVHQ4EFgQUHsN9t35DW4U6wZYQ0jOEVIDp7xMwDQYJKoZIhvcNAQEL 15 | BQADggEBAA6peynGrNJfiD9yVvtZTfmnuAUTHlHJ9iUMmugo65J9D27ZgStgGTqE 16 | CwvhoYH+fbeJBB/WrWns48piWYXOUSoyPhCI/hGqGl4uu/d66OBBHLlOE6v2W5x0 17 | EsOxAsTkobcG4y9CBgnoVdzcBuO+wfUFQDZYJkXFYYvK2GxJqJBTlJSqWITLute9 18 | 6e8BjytBzZnbI9AIopl+pt+ErMDQfkS1WIHDyg4ER0gpLDrQeJq0JxGuK/d6giYy 19 | hDtsGhz2ibk4DWVxYAlUIPwkvAnDMGW2AwbdAiX7HyqO2OqGwwberhiGZDXqLSoL 20 | LYor6j123IpnraOTz/NQjFMQ7ZyHVAs= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /test/fixtures/genkey.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 3 | 4 | # Generate ca key and pem 5 | openssl req -new -x509 -nodes -days 9999 -config ca.cnf -keyout ca.key -out ca.pem 6 | 7 | # Generate server key 8 | # openssl genrsa -out server.key 2048 9 | openssl genrsa -out agenttest-key.pem 2048 10 | 11 | # Generate a certificate signing request for server.key 12 | # openssl req -new -key server.key -out server.csr 13 | openssl req -new -key agenttest-key.pem -out agenttest.csr -config server.cnf 14 | 15 | # Sign the csr with the ca certificate, generating server.pem 16 | openssl x509 -req -extfile server.cnf -days 999 -passin "pass:password" -extensions v3_req -in agenttest.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out agenttest-cert.pem 17 | 18 | # openssl req -new -key agenttest-key.pem -out certrequest.csr 19 | rm agenttest.csr ca.srl 20 | # http://www.hacksparrow.com/node-js-https-ssl-certificate.html 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/server.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 2048 3 | days = 9999 4 | distinguished_name = req_distinguished_name 5 | attributes = req_attributes 6 | prompt = no 7 | x509_extensions = v3_ca 8 | req_extensions = v3_req 9 | 10 | [ req_distinguished_name ] 11 | C = SE 12 | ST = Stockholm 13 | L = Stockholm 14 | CN = agentkeepalive.com 15 | 16 | [ req_attributes ] 17 | challengePassword = password 18 | 19 | [ v3_ca ] 20 | authorityInfoAccess = @issuer_info 21 | 22 | [ v3_req ] 23 | subjectAltName = @alt_names 24 | # Extensions to add to a certificate request 25 | basicConstraints = CA:FALSE 26 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 27 | 28 | [ issuer_info ] 29 | OCSP;URI.0 = http://ocsp.example.com/ 30 | caIssuers;URI.0 = http://example.com/ca.cert 31 | 32 | [ alt_names ] 33 | IP.1 = 127.0.0.1 34 | IP.2 = ::1 35 | DNS.1 = localhost 36 | -------------------------------------------------------------------------------- /test/fixtures/ts/index.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import { constants, HttpAgent, HttpsAgent, HttpOptions, HttpsOptions, AgentStatus } from '../../..'; 3 | import * as assert from 'assert'; 4 | 5 | assert(constants.CREATE_ID); 6 | assert(constants.CREATE_HTTPS_CONNECTION); 7 | assert(constants.CURRENT_ID); 8 | 9 | const httpOpt: HttpOptions = { 10 | maxSockets: 100, 11 | maxFreeSockets: 10, 12 | timeout: 60000, // active socket keepalive for 60 seconds 13 | freeSocketTimeout: 30000, // free socket keepalive for 30 seconds 14 | }; 15 | const keepaliveAgent = new HttpAgent(httpOpt); 16 | 17 | const options = { 18 | host: 'cnodejs.org', 19 | port: 80, 20 | path: '/', 21 | method: 'GET', 22 | agent: keepaliveAgent, 23 | }; 24 | 25 | const req = http.request(options, res => { 26 | console.log('STATUS: ' + res.statusCode); 27 | console.log('HEADERS: ' + JSON.stringify(res.headers)); 28 | res.setEncoding('utf8'); 29 | res.on('data', function (chunk) { 30 | console.log('BODY: ' + chunk); 31 | }); 32 | }); 33 | 34 | req.on('error', e => { 35 | console.log('problem with request: ' + e.message); 36 | }); 37 | 38 | req.end(); 39 | 40 | setTimeout(() => { 41 | if (keepaliveAgent.statusChanged) { 42 | const httpAgentStatus: AgentStatus = keepaliveAgent.getCurrentStatus(); 43 | console.log('[%s] agent status changed: %j', Date(), httpAgentStatus); 44 | } 45 | }, 2000); 46 | 47 | // https 48 | const httpsOpt: HttpsOptions = { 49 | maxSockets: 100, 50 | maxFreeSockets: 10, 51 | timeout: 60000, // active socket keepalive for 60 seconds 52 | freeSocketTimeout: 30000, // free socket keepalive for 30 seconds 53 | }; 54 | const keepaliveHttpsAgent = new HttpsAgent(httpsOpt); 55 | const httpsAgentStatus: AgentStatus = keepaliveHttpsAgent.getCurrentStatus(); 56 | assert(httpsAgentStatus); 57 | -------------------------------------------------------------------------------- /test/fixtures/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "noImplicitAny": false, 7 | "experimentalDecorators": true, 8 | "emitDecoratorMetadata": true, 9 | "charset": "utf8", 10 | "allowJs": false, 11 | "pretty": false, 12 | "baseUrl": ".", 13 | "strict": true, 14 | "resolveJsonModule": true, 15 | "noEmitOnError": false, 16 | "noUnusedLocals": false, 17 | "noUnusedParameters": true, 18 | "allowUnreachableCode": false, 19 | "allowUnusedLabels": false, 20 | "noFallthroughCasesInSwitch": true, 21 | "skipLibCheck": false, 22 | "skipDefaultLibCheck": false, 23 | "importHelpers": false, 24 | "inlineSourceMap": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/http_agent.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const http = require('http'); 5 | const urlparse = require('url').parse; 6 | const pedding = require('pedding'); 7 | const mm = require('mm'); 8 | const HttpAgent = require('..').HttpAgent; 9 | const { 10 | CURRENT_ID, 11 | SOCKET_NAME, 12 | SOCKET_CREATED_TIME, 13 | SOCKET_REQUEST_COUNT, 14 | SOCKET_REQUEST_FINISHED_COUNT, 15 | } = require('..').constants; 16 | 17 | describe('test/agent.test.js', () => { 18 | const agentkeepalive = new HttpAgent({ 19 | keepAliveTimeout: 1000, 20 | maxSockets: 5, 21 | maxFreeSockets: 5, 22 | }); 23 | 24 | let port = null; 25 | const app = http.createServer((req, res) => { 26 | if (req.url === '/error') { 27 | res.destroy(); 28 | return; 29 | } else if (req.url === '/hang') { 30 | console.log('[new request] %s %s', req.method, req.url); 31 | // Wait forever. 32 | return; 33 | } else if (req.url === '/remote_close') { 34 | setTimeout(() => { 35 | req.connection.end(); 36 | }, 500); 37 | } 38 | const info = urlparse(req.url, true); 39 | if (info.query.timeout) { 40 | setTimeout(() => { 41 | res.end(info.query.timeout); 42 | }, parseInt(info.query.timeout)); 43 | return; 44 | } 45 | res.end(JSON.stringify({ 46 | info, 47 | url: req.url, 48 | headers: req.headers, 49 | socket: req.socket._getpeername(), 50 | })); 51 | }); 52 | 53 | before(done => { 54 | app.listen(0, () => { 55 | port = app.address().port; 56 | done(); 57 | }); 58 | }); 59 | 60 | afterEach(mm.restore); 61 | 62 | after(done => setTimeout(() => { 63 | agentkeepalive.destroy(); 64 | done(); 65 | }, 1500)); 66 | 67 | it('should default options set right', () => { 68 | const agent = agentkeepalive; 69 | assert(agent.keepAlive === true); 70 | assert(agent.keepAliveMsecs === 1000); 71 | assert(agent.maxSockets === 5); 72 | assert(agent.maxFreeSockets === 5); 73 | assert(agent.timeout === 8000); 74 | assert(agent.options.timeout === 8000); 75 | assert(agent.freeSocketKeepAliveTimeout === 1000); 76 | assert(agent.options.freeSocketTimeout === 1000); 77 | assert(!agent.socketActiveTTL); 78 | assert(agent.socketActiveTTL === 0); 79 | assert(agent.options.socketActiveTTL === 0); 80 | }); 81 | 82 | let remotePort = null; 83 | 84 | it('should request with connection: keep-alive with http.Agent(keepAlive=true)', done => { 85 | const agent = new http.Agent({ 86 | keepAlive: true, 87 | }); 88 | const req = http.request({ 89 | method: 'GET', 90 | port, 91 | path: '/', 92 | agent, 93 | }, res => { 94 | assert(res.statusCode === 200); 95 | const chunks = []; 96 | res.on('data', data => { 97 | chunks.push(data); 98 | }); 99 | res.on('end', () => { 100 | const data = JSON.parse(Buffer.concat(chunks)); 101 | assert(data.headers.connection === 'keep-alive'); 102 | done(); 103 | }); 104 | }); 105 | req.end(); 106 | }); 107 | 108 | // NOTE: The test is irrelevant since NodeJS >= v19 as behavior is changed and it has default `keep-alive` 109 | // see https://nodejs.org/en/blog/announcements/v19-release-announce#https11-keepalive-by-default 110 | it.skip('should request with connection: close with http.Agent()', done => { 111 | const req = http.request({ 112 | method: 'GET', 113 | port, 114 | path: '/', 115 | }, res => { 116 | assert(res.statusCode === 200); 117 | const chunks = []; 118 | res.on('data', data => { 119 | chunks.push(data); 120 | }); 121 | res.on('end', () => { 122 | const data = JSON.parse(Buffer.concat(chunks)); 123 | assert(data.headers.connection === 'close'); 124 | done(); 125 | }); 126 | }); 127 | req.end(); 128 | }); 129 | 130 | it('should destroy inactivity socket timeout by agent itself', done => { 131 | const name = 'localhost:' + port + ':'; 132 | const agentkeepalive = new HttpAgent({ 133 | freeSocketKeepAliveTimeout: '5s', 134 | timeout: '1s', 135 | }); 136 | assert(agentkeepalive.options.freeSocketTimeout === 5000); 137 | assert(agentkeepalive.options.timeout === 1000); 138 | assert(!agentkeepalive.sockets[name]); 139 | assert(!agentkeepalive.freeSockets[name]); 140 | http.get({ 141 | agent: agentkeepalive, 142 | port, 143 | path: '/', 144 | }, res => { 145 | assert(res.statusCode === 200); 146 | const chunks = []; 147 | res.resume(); 148 | res.on('data', data => { 149 | chunks.push(data); 150 | }); 151 | res.on('end', () => { 152 | const buf = Buffer.concat(chunks); 153 | console.log('end and got %d bytes', buf.length); 154 | const data = JSON.parse(buf); 155 | remotePort = data.socket.port; 156 | assert(data.headers.connection === 'keep-alive'); 157 | assert(agentkeepalive.sockets[name]); 158 | assert(!agentkeepalive.freeSockets[name]); 159 | setTimeout(() => { 160 | assert(!agentkeepalive.sockets[name]); 161 | assert(agentkeepalive.freeSockets[name]); 162 | assert(agentkeepalive.freeSockets[name].length === 1); 163 | 164 | // request /hang timeout 165 | http.get({ 166 | agent: agentkeepalive, 167 | port, 168 | path: '/hang', 169 | }, () => { 170 | assert(false, 'should not run this'); 171 | }).on('error', err => { 172 | assert(err.message === 'Socket timeout'); 173 | assert(err.code === 'ERR_SOCKET_TIMEOUT'); 174 | done(); 175 | }); 176 | }, 20); 177 | }); 178 | }); 179 | }); 180 | 181 | it('should let request handle the socket timeout', done => { 182 | const name = 'localhost:' + port + ':'; 183 | const agentkeepalive = new HttpAgent({ 184 | freeSocketKeepAliveTimeout: '5s', 185 | timeout: '1s', 186 | }); 187 | assert(agentkeepalive.options.freeSocketTimeout === 5000); 188 | assert(agentkeepalive.options.timeout === 1000); 189 | assert(!agentkeepalive.sockets[name]); 190 | assert(!agentkeepalive.freeSockets[name]); 191 | http.get({ 192 | agent: agentkeepalive, 193 | port, 194 | path: '/', 195 | }, res => { 196 | assert(res.statusCode === 200); 197 | const chunks = []; 198 | res.resume(); 199 | res.on('data', data => { 200 | chunks.push(data); 201 | }); 202 | res.on('end', () => { 203 | const buf = Buffer.concat(chunks); 204 | console.log('end and got %d bytes', buf.length); 205 | const data = JSON.parse(buf); 206 | remotePort = data.socket.port; 207 | assert(data.headers.connection === 'keep-alive'); 208 | assert(agentkeepalive.sockets[name]); 209 | assert(!agentkeepalive.freeSockets[name]); 210 | setTimeout(() => { 211 | assert(!agentkeepalive.sockets[name]); 212 | assert(agentkeepalive.freeSockets[name]); 213 | assert(agentkeepalive.freeSockets[name].length === 1); 214 | 215 | // request /hang timeout 216 | let handleTimeout = false; 217 | const req = http.get({ 218 | agent: agentkeepalive, 219 | port, 220 | path: '/hang', 221 | timeout: 2000, 222 | }, () => { 223 | assert(false, 'should not run this'); 224 | }).on('error', err => { 225 | assert(handleTimeout); 226 | // TODO: should be a better error message than "socket hang up" 227 | assert(err.message === 'socket hang up'); 228 | assert(err.code === 'ECONNRESET'); 229 | done(); 230 | }); 231 | req.on('timeout', () => { 232 | handleTimeout = true; 233 | req.abort(); 234 | }); 235 | }, 20); 236 | }); 237 | }); 238 | }); 239 | 240 | it('should request / 200 status', done => { 241 | const name = 'localhost:' + port + ':'; 242 | assert(!agentkeepalive.sockets[name]); 243 | assert(!agentkeepalive.freeSockets[name]); 244 | http.get({ 245 | agent: agentkeepalive, 246 | port, 247 | path: '/', 248 | }, res => { 249 | assert(res.statusCode === 200); 250 | const chunks = []; 251 | res.on('data', data => { 252 | chunks.push(data); 253 | }); 254 | res.on('end', () => { 255 | const data = JSON.parse(Buffer.concat(chunks)); 256 | remotePort = data.socket.port; 257 | assert(data.headers.connection === 'keep-alive'); 258 | assert(agentkeepalive.sockets[name]); 259 | assert(!agentkeepalive.freeSockets[name]); 260 | setTimeout(() => { 261 | assert(!agentkeepalive.sockets[name]); 262 | assert(agentkeepalive.freeSockets[name]); 263 | assert(agentkeepalive.freeSockets[name].length === 1); 264 | done(); 265 | }, 20); 266 | }); 267 | }); 268 | 269 | const status = agentkeepalive.getCurrentStatus(); 270 | assert(status.createSocketCount === 1); 271 | assert(status.timeoutSocketCount === 0); 272 | assert(status.sockets[name] === 1); 273 | assert(!status.freeSockets[name]); 274 | }); 275 | 276 | it('should mock CURRENT_ID cross MAX_SAFE_INTEGER', _done => { 277 | const agent = new HttpAgent({ 278 | timeout: 1000, 279 | freeSocketTimeout: 1000, 280 | maxSockets: 10, 281 | maxFreeSockets: 5, 282 | }); 283 | agent[CURRENT_ID] = Number.MAX_SAFE_INTEGER - 1; 284 | const done = pedding(300, () => { 285 | // only allow 10 sockets 286 | assert(agent[CURRENT_ID] === 9); 287 | setImmediate(() => { 288 | const name = 'localhost:' + port + ':'; 289 | assert(agent.freeSockets[name].length === 5); 290 | agent.destroy(); 291 | _done(); 292 | }); 293 | }); 294 | function request(callback) { 295 | http.get({ 296 | agent, 297 | port, 298 | path: '/', 299 | }, res => { 300 | assert(res.statusCode === 200); 301 | res.resume(); 302 | res.on('end', callback); 303 | }); 304 | } 305 | for (let i = 0; i < 300; i++) { 306 | request(done); 307 | } 308 | }); 309 | 310 | it('should work on timeout same as freeSocketTimeout', done => { 311 | const agent = new HttpAgent({ 312 | timeout: 1000, 313 | freeSocketTimeout: 1000, 314 | }); 315 | 316 | http.get({ 317 | agent, 318 | port, 319 | path: '/', 320 | }, res => { 321 | const socket1 = res.socket; 322 | const timeout = socket1.timeout || socket1._idleTimeout; 323 | assert(timeout === 1000); 324 | assert(res.statusCode === 200); 325 | res.resume(); 326 | res.on('end', () => { 327 | setImmediate(() => { 328 | const timeout = socket1.timeout || socket1._idleTimeout; 329 | assert(timeout === 1000); 330 | http.get({ 331 | agent, 332 | port, 333 | path: '/', 334 | }, res => { 335 | const socket2 = res.socket; 336 | assert(socket2 === socket1); 337 | const timeout = socket2.timeout || socket2._idleTimeout; 338 | assert(timeout === 1000); 339 | assert(res.statusCode === 200); 340 | res.resume(); 341 | res.on('end', done); 342 | }); 343 | }); 344 | }); 345 | }); 346 | }); 347 | 348 | it('should work on freeSocketTimeout = 0', done => { 349 | const agent = new HttpAgent({ 350 | timeout: 100, 351 | freeSocketTimeout: 0, 352 | }); 353 | 354 | http.get({ 355 | agent, 356 | port, 357 | path: '/?timeout=80', 358 | }, res => { 359 | const socket1 = res.socket; 360 | const timeout = socket1.timeout || socket1._idleTimeout; 361 | assert(timeout === 100); 362 | assert(res.statusCode === 200); 363 | res.resume(); 364 | res.on('end', () => { 365 | setTimeout(() => { 366 | http.get({ 367 | agent, 368 | port, 369 | path: '/', 370 | }, res => { 371 | const socket2 = res.socket; 372 | assert(socket2 === socket1); 373 | const timeout = socket2.timeout || socket2._idleTimeout; 374 | assert(timeout === 100); 375 | assert(res.statusCode === 200); 376 | res.resume(); 377 | res.on('end', done); 378 | }); 379 | }, 80); 380 | }); 381 | }); 382 | }); 383 | 384 | it('should createConnection error', done => { 385 | const agent = new HttpAgent(); 386 | mm.error(require('http').Agent.prototype, 'createConnection', 'mock createConnection error'); 387 | http.get({ 388 | agent, 389 | port, 390 | path: '/', 391 | }).on('error', err => { 392 | assert(err); 393 | assert(err.message === 'mock createConnection error'); 394 | done(); 395 | }); 396 | }); 397 | 398 | it('should keepSocketAlive return false, no use any socket', done => { 399 | const agent = new HttpAgent(); 400 | mm(require('http').Agent.prototype, 'keepSocketAlive', () => { 401 | return false; 402 | }); 403 | http.get({ 404 | agent, 405 | port, 406 | path: '/', 407 | }, res => { 408 | const socket1 = res.socket; 409 | res.resume(); 410 | res.on('end', () => { 411 | setImmediate(() => { 412 | http.get({ 413 | agent, 414 | port, 415 | path: '/', 416 | }, res => { 417 | const socket2 = res.socket; 418 | assert(socket2 !== socket1); 419 | res.resume(); 420 | res.on('end', done); 421 | }); 422 | }); 423 | }); 424 | }); 425 | }); 426 | 427 | it('should agent emit socket error event', done => { 428 | const agent = new HttpAgent({ 429 | timeout: 100, 430 | }); 431 | const req = http.get({ 432 | agent, 433 | port, 434 | path: '/hang', 435 | }); 436 | // remove mocha default handler 437 | const originalException = process.listeners('uncaughtException').pop(); 438 | process.removeListener('uncaughtException', originalException); 439 | process.once('uncaughtException', err => { 440 | // ignore future req error 441 | req.on('error', () => {}); 442 | process.on('uncaughtException', originalException); 443 | assert(err); 444 | assert(err.message === 'Socket timeout'); 445 | done(); 446 | }); 447 | }); 448 | 449 | it('should mock socket error', done => { 450 | done = pedding(2, done); 451 | const agent = new HttpAgent({ 452 | timeout: 100, 453 | }); 454 | const req = http.get({ 455 | agent, 456 | port, 457 | path: '/hang', 458 | }); 459 | req.on('socket', socket => { 460 | // remove req error listener 461 | const listener = socket.listeners('error').pop(); 462 | socket.removeListener('error', listener); 463 | // must destroy before emit error 464 | socket.destroy(); 465 | socket.emit('error', new Error('mock socket error')); 466 | }).on('error', err => { 467 | assert(err); 468 | assert(err.message === 'socket hang up'); 469 | done(); 470 | }); 471 | // remove mocha default handler 472 | const originalException = process.listeners('uncaughtException').pop(); 473 | process.removeListener('uncaughtException', originalException); 474 | assert(process.listeners('uncaughtException').length === 0); 475 | process.once('uncaughtException', err => { 476 | process.on('uncaughtException', originalException); 477 | assert(err); 478 | assert(err.message === 'mock socket error'); 479 | done(); 480 | }); 481 | }); 482 | 483 | it('should request again and use the same socket', done => { 484 | const name = 'localhost:' + port + ':'; 485 | assert(!agentkeepalive.sockets[name]); 486 | assert(agentkeepalive.freeSockets[name]); 487 | assert(agentkeepalive.freeSockets[name].length === 1); 488 | 489 | http.get({ 490 | agent: agentkeepalive, 491 | port, 492 | path: '/foo', 493 | }, res => { 494 | assert(res.statusCode === 200); 495 | const chunks = []; 496 | res.on('data', data => { 497 | chunks.push(data); 498 | }); 499 | res.on('end', () => { 500 | const data = JSON.parse(Buffer.concat(chunks)); 501 | assert(data.socket.port === remotePort); 502 | 503 | assert(agentkeepalive.sockets[name]); 504 | assert(!agentkeepalive.freeSockets[name]); 505 | setTimeout(() => { 506 | const status = agentkeepalive.getCurrentStatus(); 507 | assert(status.createSocketCount === 1); 508 | assert(status.closeSocketCount === 0); 509 | assert(status.timeoutSocketCount === 0); 510 | assert(status.requestCount === 2); 511 | assert(!status.sockets[name]); 512 | assert(status.freeSockets[name]); 513 | assert(status.freeSockets[name] === 1); 514 | done(); 515 | }, 10); 516 | }); 517 | }); 518 | assert(agentkeepalive.sockets[name]); 519 | assert(agentkeepalive.sockets[name].length === 1); 520 | assert(!agentkeepalive.freeSockets[name]); 521 | }); 522 | 523 | it('should remove keepalive socket when server side destroy()', done => { 524 | const agent = new HttpAgent({ 525 | keepAliveTimeout: 1000, 526 | maxSockets: 5, 527 | maxFreeSockets: 5, 528 | }); 529 | 530 | http.get({ 531 | agent, 532 | port, 533 | path: '/foo', 534 | }, res => { 535 | assert(res.statusCode === 200); 536 | const chunks = []; 537 | res.on('data', data => { 538 | chunks.push(data); 539 | }); 540 | res.on('end', () => { 541 | const data = JSON.parse(Buffer.concat(chunks)); 542 | assert(data.socket.port); 543 | setTimeout(next, 1); 544 | }); 545 | }); 546 | 547 | function next() { 548 | const name = 'localhost:' + port + ':'; 549 | assert(!agent.sockets[name]); 550 | assert(agent.freeSockets[name] && agent.freeSockets[name].length === 1); 551 | 552 | const req = http.get({ 553 | agent, 554 | port, 555 | path: '/error', 556 | }, () => { 557 | assert.fail('should not call this'); 558 | }); 559 | req.on('error', err => { 560 | assert(err.message === 'socket hang up'); 561 | assert(agent.sockets[name].length === 1); 562 | assert(!agent.freeSockets[name]); 563 | setTimeout(() => { 564 | assert(!agent.sockets[name]); 565 | assert(!agent.freeSockets[name]); 566 | done(); 567 | }, 10); 568 | }); 569 | assert(agent.sockets[name].length === 1); 570 | assert(!agent.freeSockets[name]); 571 | } 572 | }); 573 | 574 | it('should remove socket when socket.destroy()', done => { 575 | const agentkeepalive = new HttpAgent({ 576 | freeSocketTimeout: 1000, 577 | maxSockets: 5, 578 | maxFreeSockets: 5, 579 | }); 580 | const name = 'localhost:' + port + ':'; 581 | assert(!agentkeepalive.sockets[name]); 582 | assert(!agentkeepalive.freeSockets[name]); 583 | http.get({ 584 | agent: agentkeepalive, 585 | port, 586 | path: '/', 587 | }, res => { 588 | assert(res.statusCode === 200); 589 | res.resume(); 590 | res.on('end', () => { 591 | assert(agentkeepalive.sockets[name].length === 1); 592 | assert(!agentkeepalive.freeSockets[name]); 593 | setTimeout(() => { 594 | assert(!agentkeepalive.sockets[name]); 595 | assert(agentkeepalive.freeSockets[name].length === 1); 596 | agentkeepalive.freeSockets[name][0].destroy(); 597 | setTimeout(() => { 598 | assert(!agentkeepalive.sockets[name]); 599 | assert(!agentkeepalive.freeSockets[name]); 600 | done(); 601 | }, 10); 602 | }, 10); 603 | }); 604 | }).on('error', done); 605 | }); 606 | 607 | it('should use new socket when hit the max keepalive time: 1000ms', done => { 608 | const agentkeepalive = new HttpAgent({ 609 | freeSocketTimeout: 1000, 610 | maxSockets: 5, 611 | maxFreeSockets: 5, 612 | }); 613 | const name = 'localhost:' + port + ':'; 614 | assert(!agentkeepalive.sockets[name]); 615 | assert(!agentkeepalive.freeSockets[name]); 616 | http.get({ 617 | agent: agentkeepalive, 618 | port, 619 | path: '/', 620 | }, res => { 621 | assert(res.statusCode === 200); 622 | let lastPort = null; 623 | res.on('data', data => { 624 | data = JSON.parse(data); 625 | lastPort = data.socket.port; 626 | assert(lastPort > 0); 627 | }); 628 | res.on('end', () => { 629 | assert(agentkeepalive.sockets[name].length === 1); 630 | assert(!agentkeepalive.freeSockets[name]); 631 | 632 | // free keepAlive socket timeout and destroy 633 | setTimeout(() => { 634 | assert(!agentkeepalive.sockets[name]); 635 | assert(!agentkeepalive.freeSockets[name]); 636 | http.get({ 637 | agent: agentkeepalive, 638 | port, 639 | path: '/', 640 | }, res => { 641 | assert(res.statusCode === 200); 642 | res.on('data', data => { 643 | data = JSON.parse(data); 644 | assert(data.socket.port > 0); 645 | assert(data.socket.port !== lastPort); 646 | }); 647 | res.on('end', done); 648 | }); 649 | }, 2000); 650 | }); 651 | }); 652 | }); 653 | 654 | it('should disable keepalive when keepAlive=false', done => { 655 | const name = 'localhost:' + port + ':'; 656 | const agent = new HttpAgent({ 657 | keepAlive: false, 658 | }); 659 | assert(agent.keepAlive === false); 660 | 661 | http.get({ 662 | agent, 663 | port, 664 | path: '/', 665 | }, res => { 666 | assert(res.statusCode === 200); 667 | res.on('data', data => { 668 | assert(JSON.parse(data).headers.connection === 'close'); 669 | }); 670 | res.on('end', () => { 671 | assert(agent.sockets[name].length === 1); 672 | assert(!agent.freeSockets[name]); 673 | setTimeout(() => { 674 | assert(!agent.sockets[name]); 675 | assert(!agent.freeSockets[name]); 676 | done(); 677 | }, 10); 678 | }); 679 | }); 680 | }); 681 | 682 | it('should not keepalive when client.abort()', done => { 683 | const agentkeepalive = new HttpAgent({ 684 | freeSocketTimeout: 1000, 685 | maxSockets: 5, 686 | maxFreeSockets: 5, 687 | }); 688 | const name = 'localhost:' + port + ':'; 689 | assert(!agentkeepalive.sockets[name]); 690 | const req = http.get({ 691 | agent: agentkeepalive, 692 | port, 693 | path: '/', 694 | }, () => { 695 | assert.fail('should not call this.'); 696 | }); 697 | req.on('error', err => { 698 | assert(err.message, 'socket hang up'); 699 | assert(!agentkeepalive.sockets[name]); 700 | assert(!agentkeepalive.freeSockets[name]); 701 | done(); 702 | }); 703 | process.nextTick(() => { 704 | req.abort(); 705 | }); 706 | assert(agentkeepalive.sockets[name].length === 1); 707 | }); 708 | 709 | it('should keep 1 socket', done => { 710 | const name = 'localhost:' + port + ':'; 711 | const agent = new HttpAgent({ 712 | maxSockets: 1, 713 | maxFreeSockets: 1, 714 | }); 715 | let lastPort = null; 716 | http.get({ 717 | agent, 718 | port, 719 | path: '/', 720 | }, res => { 721 | assert(agent.sockets[name].length === 1); 722 | assert(agent.requests[name].length === 1); 723 | assert(res.statusCode === 200); 724 | res.on('data', data => { 725 | data = JSON.parse(data); 726 | lastPort = data.socket.port; 727 | assert(lastPort > 0); 728 | }); 729 | res.on('end', () => { 730 | // should be reuse 731 | process.nextTick(() => { 732 | assert(agent.sockets[name].length === 1); 733 | assert(!agent.freeSockets[name]); 734 | }); 735 | }); 736 | }); 737 | 738 | http.get({ 739 | agent, 740 | port, 741 | path: '/', 742 | }, res => { 743 | assert(agent.sockets[name].length === 1); 744 | assert(!agent.requests[name]); 745 | assert(res.statusCode === 200); 746 | 747 | res.on('data', data => { 748 | data = JSON.parse(data); 749 | assert(data.socket.port === lastPort); 750 | }); 751 | res.on('end', () => { 752 | setTimeout(() => { 753 | // should keepalive 1 socket 754 | assert(!agent.sockets[name]); 755 | assert(agent.freeSockets[name].length === 1); 756 | done(); 757 | }, 10); 758 | }); 759 | }); 760 | 761 | // has 1 request pedding in the requests queue 762 | assert(agent.requests[name].length === 1); 763 | }); 764 | 765 | it('should keep 1 free socket', done => { 766 | const name = 'localhost:' + port + ':'; 767 | const agent = new HttpAgent({ 768 | maxSockets: 2, 769 | maxFreeSockets: 1, 770 | }); 771 | let lastPort = null; 772 | http.get({ 773 | agent, 774 | port, 775 | path: '/', 776 | }, res => { 777 | assert(agent.sockets[name]); 778 | assert(res.statusCode === 200); 779 | 780 | res.on('data', data => { 781 | data = JSON.parse(data); 782 | lastPort = data.socket.port; 783 | assert(lastPort > 0); 784 | }); 785 | res.on('end', () => { 786 | // should be reuse 787 | setTimeout(() => { 788 | assert(agent.freeSockets[name].length === 1); 789 | }, 100); 790 | }); 791 | }); 792 | 793 | http.get({ 794 | agent, 795 | port, 796 | path: '/', 797 | }, res => { 798 | assert(agent.sockets[name]); 799 | assert(res.statusCode === 200); 800 | res.on('data', data => { 801 | data = JSON.parse(data); 802 | assert(data.socket.port !== lastPort); 803 | }); 804 | res.on('end', () => { 805 | setTimeout(() => { 806 | // should keepalive 1 socket 807 | assert(!agent.sockets[name]); 808 | assert(agent.freeSockets[name].length === 1); 809 | done(); 810 | }, 100); 811 | }); 812 | }); 813 | assert(!agent.requests[name]); 814 | }); 815 | 816 | it('should keep 2 free socket', done => { 817 | done = pedding(2, done); 818 | const name = 'localhost:' + port + ':'; 819 | const agent = new HttpAgent({ 820 | maxSockets: 2, 821 | maxFreeSockets: 2, 822 | }); 823 | let lastPort = null; 824 | http.get({ 825 | agent, 826 | port, 827 | path: '/', 828 | }, res => { 829 | assert(agent.sockets[name].length); 830 | assert(res.statusCode === 200); 831 | res.on('data', data => { 832 | data = JSON.parse(data); 833 | lastPort = data.socket.port; 834 | assert(lastPort > 0); 835 | }); 836 | res.on('end', () => { 837 | // should be reuse 838 | process.nextTick(() => { 839 | assert(agent.freeSockets[name]); 840 | done(); 841 | }); 842 | }); 843 | }); 844 | 845 | http.get({ 846 | agent, 847 | port, 848 | path: '/', 849 | }, res => { 850 | assert(agent.sockets[name].length); 851 | assert(res.statusCode === 200); 852 | res.on('data', data => { 853 | data = JSON.parse(data); 854 | assert(data.socket.port !== lastPort); 855 | }); 856 | res.on('end', () => { 857 | setTimeout(() => { 858 | // should keepalive 2 free sockets 859 | assert(!agent.sockets[name]); 860 | assert(agent.freeSockets[name].length === 2); 861 | done(); 862 | }, 10); 863 | }); 864 | }); 865 | assert(!agent.requests[name]); 866 | }); 867 | 868 | it('should request /remote_close 200 status, after 500ms free socket close', done => { 869 | const name = 'localhost:' + port + ':'; 870 | assert(!agentkeepalive.sockets[name]); 871 | 872 | http.get({ 873 | agent: agentkeepalive, 874 | port, 875 | path: '/remote_close', 876 | }, res => { 877 | assert(res.statusCode === 200); 878 | res.resume(); 879 | res.on('end', () => { 880 | assert(agentkeepalive.sockets[name]); 881 | assert(!agentkeepalive.freeSockets[name]); 882 | setTimeout(() => { 883 | assert(!agentkeepalive.sockets[name]); 884 | assert(!agentkeepalive.freeSockets[name]); 885 | done(); 886 | }, 1000); 887 | }); 888 | }); 889 | }); 890 | 891 | it('should fire req timeout callback the first use socket', done => { 892 | done = pedding(2, done); 893 | const agent = new HttpAgent({ 894 | maxSockets: 2, 895 | maxFreeSockets: 2, 896 | }); 897 | http.get({ 898 | agent, 899 | port, 900 | path: '/', 901 | }, res => { 902 | assert(res.statusCode === 200); 903 | res.resume(); 904 | res.on('end', () => { 905 | const lastStatus = agent.getCurrentStatus(); 906 | const req = http.get({ 907 | agent, 908 | port, 909 | path: '/hang', 910 | }, () => { 911 | assert.fail('should not call this'); 912 | }); 913 | req.setTimeout(100, () => { 914 | const status = agent.getCurrentStatus(); 915 | assert(status.timeoutSocketCount - lastStatus.timeoutSocketCount === 1); 916 | req.abort(); 917 | done(); 918 | }); 919 | req.on('error', err => { 920 | assert(err.message === 'socket hang up'); 921 | done(); 922 | }); 923 | }); 924 | }); 925 | }); 926 | 927 | it('should fire req timeout callback the second use socket', done => { 928 | done = pedding(2, done); 929 | const agent = new HttpAgent({ 930 | maxSockets: 2, 931 | maxFreeSockets: 2, 932 | }); 933 | http.get({ 934 | agent, 935 | port, 936 | path: '/', 937 | }, res => { 938 | assert(res.statusCode === 200); 939 | res.resume(); 940 | res.on('end', () => { 941 | const lastStatus = agent.getCurrentStatus(); 942 | assert(lastStatus.createSocketCount === 1); 943 | // make sure reuse the same socket 944 | setImmediate(() => { 945 | const req = http.get({ 946 | agent, 947 | port, 948 | path: '/hang', 949 | }, () => { 950 | assert.fail('should not call this'); 951 | }); 952 | req.setTimeout(100, () => { 953 | const status = agent.getCurrentStatus(); 954 | assert(status.createSocketCount === 1); 955 | assert(status.timeoutSocketCount - lastStatus.timeoutSocketCount === 1); 956 | req.abort(); 957 | done(); 958 | }); 959 | req.on('error', err => { 960 | assert(err.message === 'socket hang up'); 961 | done(); 962 | }); 963 | }); 964 | }); 965 | }); 966 | }); 967 | 968 | it('should free socket timeout work', done => { 969 | const name = 'localhost:' + port + ':'; 970 | const agent = new HttpAgent({ 971 | keepAliveTimeout: 100, 972 | }); 973 | 974 | let lastPort = null; 975 | http.get({ 976 | agent, 977 | port, 978 | path: '/', 979 | }, res => { 980 | assert(agent.sockets[name].length === 1); 981 | assert(res.statusCode === 200); 982 | res.on('data', data => { 983 | data = JSON.parse(data); 984 | lastPort = data.socket.port; 985 | assert(lastPort > 0); 986 | }); 987 | res.on('end', () => { 988 | process.nextTick(() => { 989 | assert(!agent.sockets[name]); 990 | assert(agent.freeSockets[name].length === 1); 991 | // free socket timeout after 100ms 992 | setTimeout(() => { 993 | assert(!agent.freeSockets[name]); 994 | done(); 995 | }, 110); 996 | }); 997 | }); 998 | }); 999 | }); 1000 | 1001 | it('should first use working socket timeout', done => { 1002 | const name = 'localhost:' + port + ':'; 1003 | const agent = new HttpAgent({ 1004 | timeout: 100, 1005 | }); 1006 | http.get({ 1007 | agent, 1008 | port, 1009 | path: '/hang', 1010 | }, () => { 1011 | throw new Error('should not run this'); 1012 | }).on('error', err => { 1013 | assert(err.message === 'Socket timeout'); 1014 | assert(err.code === 'ERR_SOCKET_TIMEOUT'); 1015 | assert(!agent.sockets[name]); 1016 | done(); 1017 | }); 1018 | assert(agent.sockets[name].length === 1); 1019 | }); 1020 | 1021 | it('should reuse working socket timeout', done => { 1022 | const name = 'localhost:' + port + ':'; 1023 | const agent = new HttpAgent({ 1024 | timeout: 100, 1025 | }); 1026 | http.get({ 1027 | agent, 1028 | port, 1029 | path: '/', 1030 | }, res => { 1031 | assert(res.statusCode === 200); 1032 | res.resume(); 1033 | res.on('end', () => { 1034 | setImmediate(() => { 1035 | http.get({ 1036 | agent, 1037 | port, 1038 | path: '/hang', 1039 | }, () => { 1040 | throw new Error('should not run this'); 1041 | }).on('error', err => { 1042 | assert(err.message === 'Socket timeout'); 1043 | assert(err.code === 'ERR_SOCKET_TIMEOUT'); 1044 | assert(!agent.sockets[name]); 1045 | done(); 1046 | }); 1047 | }); 1048 | }); 1049 | }); 1050 | assert(agent.sockets[name].length === 1); 1051 | }); 1052 | 1053 | it('should destroy free socket before timeout', done => { 1054 | const name = 'localhost:' + port + ':'; 1055 | const agent = new HttpAgent(); 1056 | let lastPort = null; 1057 | http.get({ 1058 | agent, 1059 | port, 1060 | path: '/', 1061 | }, res => { 1062 | assert(agent.sockets[name].length === 1); 1063 | assert(res.statusCode === 200); 1064 | res.on('data', data => { 1065 | data = JSON.parse(data); 1066 | lastPort = data.socket.port; 1067 | assert(lastPort > 0); 1068 | }); 1069 | res.on('end', () => { 1070 | process.nextTick(() => { 1071 | assert(!agent.sockets[name]); 1072 | assert(agent.freeSockets[name].length === 1); 1073 | agent.freeSockets[name][0].destroy(); 1074 | assert(agent.createSocketCount === 1); 1075 | setTimeout(() => { 1076 | assert(!agent.freeSockets[name]); 1077 | // new request use the new socket 1078 | http.get({ 1079 | agent, 1080 | port, 1081 | path: '/', 1082 | }, res => { 1083 | assert(agent.sockets[name].length === 1); 1084 | assert(res.statusCode === 200); 1085 | assert(agent.createSocketCount === 2); 1086 | res.resume(); 1087 | res.on('end', done); 1088 | }); 1089 | }, 10); 1090 | }); 1091 | }); 1092 | }); 1093 | assert(agent.sockets[name].length === 1); 1094 | }); 1095 | 1096 | it('should remove error socket and create new one handle pedding request', done => { 1097 | done = pedding(2, done); 1098 | const name = 'localhost:' + port + ':'; 1099 | const agent = new HttpAgent({ 1100 | maxSockets: 1, 1101 | maxFreeSockets: 1, 1102 | }); 1103 | let lastPort = null; 1104 | http.get({ 1105 | agent, 1106 | port, 1107 | path: '/error', 1108 | }, () => { 1109 | throw new Error('never run this'); 1110 | }).on('error', err => { 1111 | assert(err.message === 'socket hang up'); 1112 | }).on('close', () => done()); 1113 | 1114 | http.get({ 1115 | agent, 1116 | port, 1117 | path: '/', 1118 | }, res => { 1119 | assert(agent.sockets[name].length === 1); 1120 | const socket = agent.sockets[name][0]; 1121 | assert(socket[SOCKET_REQUEST_COUNT] === 1); 1122 | // not finish 1123 | assert(socket[SOCKET_REQUEST_FINISHED_COUNT] === 0); 1124 | assert(res.statusCode === 200); 1125 | res.on('data', data => { 1126 | data = JSON.parse(data); 1127 | lastPort = data.socket.port; 1128 | assert(lastPort > 0); 1129 | }); 1130 | res.on('end', () => { 1131 | process.nextTick(() => { 1132 | assert(!agent.sockets[name]); 1133 | assert(agent.freeSockets[name].length === 1); 1134 | const socket = agent.freeSockets[name][0]; 1135 | assert(socket[SOCKET_REQUEST_COUNT] === 1); 1136 | // request finished 1137 | assert(socket[SOCKET_REQUEST_FINISHED_COUNT] === 1); 1138 | done(); 1139 | }); 1140 | }); 1141 | }); 1142 | assert(agent.requests[name].length === 1); 1143 | }); 1144 | 1145 | it('should destroy all sockets when freeSockets is empty', done => { 1146 | done = pedding(2, done); 1147 | const name = 'localhost:' + port + ':'; 1148 | const agent = new HttpAgent(); 1149 | http.get({ 1150 | agent, 1151 | port, 1152 | path: '/', 1153 | }, res => { 1154 | http.get({ 1155 | agent, 1156 | port, 1157 | path: '/', 1158 | }).on('error', err => { 1159 | assert(err.message === 'socket hang up'); 1160 | setTimeout(() => { 1161 | assert(!agent.sockets[name]); 1162 | assert(!agent.freeSockets[name]); 1163 | done(); 1164 | }, 10); 1165 | }); 1166 | 1167 | assert(res.statusCode === 200); 1168 | res.resume(); 1169 | res.on('end', () => { 1170 | assert(agent.sockets[name].length === 2); 1171 | agent.destroy(); 1172 | done(); 1173 | }); 1174 | }); 1175 | }); 1176 | 1177 | it('should destroy both sockets and freeSockets', done => { 1178 | done = pedding(2, done); 1179 | const name = 'localhost:' + port + ':'; 1180 | const agent = new HttpAgent(); 1181 | http.get({ 1182 | agent, 1183 | port, 1184 | path: '/', 1185 | }, res => { 1186 | http.get({ 1187 | agent, 1188 | port, 1189 | path: '/', 1190 | }).on('error', err => { 1191 | assert(err.message === 'socket hang up'); 1192 | setTimeout(() => { 1193 | assert(!agent.sockets[name]); 1194 | assert(!agent.freeSockets[name]); 1195 | done(); 1196 | }, 10); 1197 | }); 1198 | 1199 | assert(res.statusCode === 200); 1200 | res.resume(); 1201 | res.on('end', () => { 1202 | assert(agent.sockets[name].length === 2); 1203 | assert(!agent.freeSockets[name]); 1204 | setImmediate(() => { 1205 | assert(agent.sockets[name].length === 1); 1206 | assert(agent.freeSockets[name].length === 1); 1207 | agent.destroy(); 1208 | done(); 1209 | }); 1210 | }); 1211 | }); 1212 | }); 1213 | 1214 | it('should keep max sockets: bugfix for orginal keepalive agent', _done => { 1215 | const name = 'localhost:' + port + ':'; 1216 | const agentkeepalive = new HttpAgent({ 1217 | maxSockets: 2, 1218 | maxFreeSockets: 2, 1219 | }); 1220 | const done = pedding(2, err => { 1221 | assert(!err); 1222 | const pool = agentkeepalive.sockets[name]; 1223 | assert(!pool); 1224 | // all sockets on free list now 1225 | const freepool = agentkeepalive.freeSockets[name]; 1226 | assert(freepool.length === 2); 1227 | _done(); 1228 | }); 1229 | 1230 | http.get({ 1231 | agent: agentkeepalive, 1232 | port, 1233 | path: '/', 1234 | }, res => { 1235 | assert(res.statusCode === 200); 1236 | res.resume(); 1237 | res.on('end', () => { 1238 | assert(agentkeepalive.sockets[name]); 1239 | setImmediate(done); 1240 | }); 1241 | }); 1242 | 1243 | http.get({ 1244 | agent: agentkeepalive, 1245 | port, 1246 | path: '/', 1247 | }, res => { 1248 | assert(res.statusCode === 200); 1249 | res.resume(); 1250 | res.on('end', () => { 1251 | assert(agentkeepalive.sockets[name]); 1252 | setImmediate(done); 1253 | }); 1254 | }); 1255 | }); 1256 | 1257 | it('should make sure max sockets limit work', _done => { 1258 | const name = 'localhost:' + port + ':'; 1259 | const agentkeepalive = new HttpAgent({ 1260 | maxSockets: 2, 1261 | maxFreeSockets: 2, 1262 | }); 1263 | const done = pedding(3, err => { 1264 | assert(!err); 1265 | const pool = agentkeepalive.sockets[name]; 1266 | assert(!pool); 1267 | // all sockets on free list now 1268 | const freepool = agentkeepalive.freeSockets[name]; 1269 | assert(freepool.length === 2); 1270 | // make sure all free sockets SOCKET_REQUEST_FINISHED_COUNT equal to SOCKET_REQUEST_COUNT 1271 | for (const s of freepool) { 1272 | assert(s[SOCKET_REQUEST_FINISHED_COUNT] === s[SOCKET_REQUEST_COUNT]); 1273 | } 1274 | _done(); 1275 | }); 1276 | 1277 | http.get({ 1278 | agent: agentkeepalive, 1279 | port, 1280 | path: '/', 1281 | }, res => { 1282 | assert(res.statusCode === 200); 1283 | res.resume(); 1284 | res.on('end', () => { 1285 | assert(agentkeepalive.sockets[name]); 1286 | setImmediate(done); 1287 | }); 1288 | }); 1289 | 1290 | http.get({ 1291 | agent: agentkeepalive, 1292 | port, 1293 | path: '/', 1294 | }, res => { 1295 | assert(res.statusCode === 200); 1296 | res.resume(); 1297 | res.on('end', () => { 1298 | assert(agentkeepalive.sockets[name]); 1299 | setImmediate(done); 1300 | }); 1301 | }); 1302 | 1303 | http.get({ 1304 | agent: agentkeepalive, 1305 | port, 1306 | path: '/', 1307 | }, res => { 1308 | assert(res.statusCode === 200); 1309 | res.resume(); 1310 | res.on('end', () => { 1311 | assert(agentkeepalive.sockets[name]); 1312 | setImmediate(() => { 1313 | // reuse free socket on addRequest 1314 | assert(agentkeepalive.freeSockets[name]); 1315 | http.get({ 1316 | agent: agentkeepalive, 1317 | port, 1318 | path: '/', 1319 | }, res => { 1320 | assert(res.statusCode === 200); 1321 | res.resume(); 1322 | res.on('end', () => { 1323 | assert(agentkeepalive.sockets[name]); 1324 | setImmediate(done); 1325 | }); 1326 | }); 1327 | }); 1328 | }); 1329 | }); 1330 | assert(agentkeepalive.sockets[name].length === 2); 1331 | assert(!agentkeepalive.freeSockets[name]); 1332 | }); 1333 | 1334 | it('should timeout and remove free socket', done => { 1335 | done = pedding(2, done); 1336 | const name = 'localhost:' + port + ':'; 1337 | const agent = new HttpAgent({ 1338 | maxSockets: 1, 1339 | maxFreeSockets: 1, 1340 | freeSocketTimeout: 1000, 1341 | }); 1342 | 1343 | const options = { 1344 | hostname: 'registry.npmjs.org', 1345 | port: 80, 1346 | path: '/', 1347 | method: 'GET', 1348 | agent, 1349 | }; 1350 | 1351 | let index = 0; 1352 | const getRequest = () => { 1353 | const currentIndex = index++; 1354 | const req = http.request(options, res => { 1355 | let size = 0; 1356 | res.on('data', chunk => { 1357 | size += chunk.length; 1358 | }); 1359 | res.on('end', () => { 1360 | console.log('#%d req end, size: %d', currentIndex, size); 1361 | done(); 1362 | }); 1363 | }); 1364 | req.on('error', done); 1365 | return req; 1366 | }; 1367 | 1368 | const req = getRequest(); 1369 | // Get a reference to the socket. 1370 | req.on('socket', sock => { 1371 | // Listen to timeout and send another request immediately. 1372 | sock.on('timeout', () => { 1373 | console.log('free socket:%s timeout', sock._host); 1374 | assert(!sock.writable); 1375 | // sock has been removed from freeSockets list 1376 | assert(!agent.freeSockets[name]); 1377 | console.log('new request send'); 1378 | getRequest().end(); 1379 | }); 1380 | }); 1381 | req.end(); 1382 | }); 1383 | 1384 | it('should not open more sockets than maxSockets when request success', done => { 1385 | done = pedding(3, done); 1386 | const name = 'localhost:' + port + ':'; 1387 | const agentkeepalive = new HttpAgent({ 1388 | keepAlive: true, 1389 | keepAliveTimeout: 1000, 1390 | maxSockets: 1, 1391 | maxFreeSockets: 1, 1392 | }); 1393 | 1394 | http.get({ 1395 | agent: agentkeepalive, 1396 | port, 1397 | path: '/hello1', 1398 | }, res => { 1399 | let info; 1400 | assert(res.statusCode === 200); 1401 | res.on('data', data => { 1402 | info = JSON.parse(data); 1403 | }); 1404 | res.on('end', () => { 1405 | assert(info.url === '/hello1'); 1406 | assert(agentkeepalive.sockets[name].length === 1); 1407 | done(); 1408 | }); 1409 | res.resume(); 1410 | }); 1411 | 1412 | http.get({ 1413 | agent: agentkeepalive, 1414 | port, 1415 | path: '/hello2', 1416 | }, res => { 1417 | let info; 1418 | assert(res.statusCode === 200); 1419 | res.on('data', data => { 1420 | info = JSON.parse(data); 1421 | }); 1422 | res.on('end', () => { 1423 | assert(info.url === '/hello2'); 1424 | assert(agentkeepalive.sockets[name].length === 1); 1425 | done(); 1426 | }); 1427 | res.resume(); 1428 | }); 1429 | 1430 | http.get({ 1431 | agent: agentkeepalive, 1432 | port, 1433 | path: '/hello3', 1434 | }, res => { 1435 | let info; 1436 | assert(res.statusCode === 200); 1437 | res.on('data', data => { 1438 | info = JSON.parse(data); 1439 | }); 1440 | res.on('end', () => { 1441 | assert(info.url === '/hello3'); 1442 | assert(agentkeepalive.sockets[name].length === 1); 1443 | done(); 1444 | }); 1445 | res.resume(); 1446 | }); 1447 | 1448 | assert(Object.keys(agentkeepalive.sockets).length === 1); 1449 | assert(agentkeepalive.sockets[name].length === 1); 1450 | }); 1451 | 1452 | it('should not open more sockets than maxSockets when request timeout', done => { 1453 | const name = 'localhost:' + port + ':'; 1454 | const agentkeepalive = new HttpAgent({ 1455 | keepAlive: true, 1456 | timeout: 1000, 1457 | maxSockets: 1, 1458 | maxFreeSockets: 1, 1459 | }); 1460 | 1461 | http.get({ 1462 | agent: agentkeepalive, 1463 | port, 1464 | path: '/hang', 1465 | }, () => { 1466 | throw new Error('should not run this'); 1467 | }) 1468 | .on('error', () => { 1469 | assert(agentkeepalive.sockets[name].length === 1); 1470 | done(); 1471 | }); 1472 | 1473 | http.get({ 1474 | agent: agentkeepalive, 1475 | port, 1476 | path: '/hang', 1477 | }, () => { 1478 | throw new Error('should not run this'); 1479 | }) 1480 | .on('error', () => { 1481 | // do noting 1482 | }); 1483 | 1484 | http.get({ 1485 | agent: agentkeepalive, 1486 | port, 1487 | path: '/hang', 1488 | }, () => { 1489 | throw new Error('should not run this'); 1490 | }) 1491 | .on('error', () => { 1492 | // do noting 1493 | }); 1494 | 1495 | assert(Object.keys(agentkeepalive.sockets).length === 1); 1496 | }); 1497 | 1498 | it('should set req.reusedSocket to true when reuse socket', done => { 1499 | const agent = new HttpAgent({ 1500 | keepAlive: true, 1501 | }); 1502 | 1503 | // First request 1504 | const req1 = http.get({ 1505 | port, 1506 | path: '/', 1507 | agent, 1508 | }, res => { 1509 | assert(res.statusCode === 200); 1510 | res.on('data', () => {}); 1511 | res.on('end', () => { 1512 | setTimeout(() => { 1513 | // Second request 1514 | const req2 = http.get({ 1515 | port, 1516 | path: '/', 1517 | agent, 1518 | }, res => { 1519 | assert(res.statusCode === 200); 1520 | res.on('data', () => {}); 1521 | res.on('end', () => { 1522 | done(); 1523 | }); 1524 | }); 1525 | // Second request reuses the socket 1526 | assert(req2.reusedSocket); 1527 | }, 10); 1528 | }); 1529 | }); 1530 | 1531 | // First request doesn't reuse the socket 1532 | assert(!req1.reusedSocket); 1533 | }); 1534 | 1535 | describe('request timeout > agent timeout', () => { 1536 | it('should use request timeout', done => { 1537 | const agent = new HttpAgent({ 1538 | keepAlive: true, 1539 | timeout: 1000, 1540 | }); 1541 | const req = http.get({ 1542 | agent, 1543 | port, 1544 | path: '/?timeout=20000', 1545 | timeout: 1500, 1546 | }, res => { 1547 | console.error(res.statusCode, res.headers); 1548 | assert.fail('should not get res here'); 1549 | }); 1550 | 1551 | let isTimeout = false; 1552 | req.on('timeout', () => { 1553 | isTimeout = true; 1554 | req.abort(); 1555 | }); 1556 | req.on('error', err => { 1557 | assert(isTimeout); 1558 | assert(err); 1559 | assert(err.message === 'socket hang up'); 1560 | assert(err.code === 'ECONNRESET'); 1561 | done(); 1562 | }); 1563 | }); 1564 | }); 1565 | 1566 | describe('keepAlive = false', () => { 1567 | it('should close socket after request', done => { 1568 | const name = 'localhost:' + port + ':'; 1569 | const agent = new HttpAgent({ 1570 | keepAlive: false, 1571 | }); 1572 | http.get({ 1573 | agent, 1574 | port, 1575 | path: '/', 1576 | }, res => { 1577 | assert(res.statusCode === 200); 1578 | res.resume(); 1579 | res.on('end', () => { 1580 | setTimeout(() => { 1581 | assert(!agent.sockets[name]); 1582 | assert(!agent.freeSockets[name]); 1583 | done(); 1584 | }, 10); 1585 | }); 1586 | }); 1587 | }); 1588 | }); 1589 | 1590 | describe('getCurrentStatus()', () => { 1591 | it('should get current agent status', () => { 1592 | const status = agentkeepalive.getCurrentStatus(); 1593 | assert.deepEqual(Object.keys(status), [ 1594 | 'createSocketCount', 'createSocketErrorCount', 'closeSocketCount', 1595 | 'errorSocketCount', 'timeoutSocketCount', 1596 | 'requestCount', 'freeSockets', 'sockets', 'requests', 1597 | ]); 1598 | }); 1599 | }); 1600 | 1601 | describe('getter statusChanged', () => { 1602 | it('should get statusChanged', () => { 1603 | const agentkeepalive = new HttpAgent({ 1604 | keepAliveTimeout: 1000, 1605 | maxSockets: 5, 1606 | maxFreeSockets: 5, 1607 | }); 1608 | assert(agentkeepalive.statusChanged === false); 1609 | assert(agentkeepalive.statusChanged === false); 1610 | agentkeepalive.createSocketCount++; 1611 | assert(agentkeepalive.createSocketCount !== agentkeepalive.createSocketCountLastCheck); 1612 | assert(agentkeepalive.statusChanged === true); 1613 | assert(agentkeepalive.createSocketCount === agentkeepalive.createSocketCountLastCheck); 1614 | assert(agentkeepalive.statusChanged === false); 1615 | 1616 | agentkeepalive.createSocketErrorCount++; 1617 | assert(agentkeepalive.createSocketErrorCount !== agentkeepalive.createSocketErrorCountLastCheck); 1618 | assert(agentkeepalive.statusChanged === true); 1619 | assert(agentkeepalive.createSocketErrorCount === agentkeepalive.createSocketErrorCountLastCheck); 1620 | assert(agentkeepalive.statusChanged === false); 1621 | 1622 | agentkeepalive.closeSocketCount++; 1623 | assert(agentkeepalive.closeSocketCount !== agentkeepalive.closeSocketCountLastCheck); 1624 | assert(agentkeepalive.statusChanged === true); 1625 | assert(agentkeepalive.closeSocketCount === agentkeepalive.closeSocketCountLastCheck); 1626 | assert(agentkeepalive.statusChanged === false); 1627 | 1628 | agentkeepalive.errorSocketCount++; 1629 | assert(agentkeepalive.errorSocketCount !== agentkeepalive.errorSocketCountLastCheck); 1630 | assert(agentkeepalive.statusChanged === true); 1631 | assert(agentkeepalive.errorSocketCount === agentkeepalive.errorSocketCountLastCheck); 1632 | assert(agentkeepalive.statusChanged === false); 1633 | 1634 | agentkeepalive.timeoutSocketCount++; 1635 | assert(agentkeepalive.timeoutSocketCount !== agentkeepalive.timeoutSocketCountLastCheck); 1636 | assert(agentkeepalive.statusChanged === true); 1637 | assert(agentkeepalive.timeoutSocketCount === agentkeepalive.timeoutSocketCountLastCheck); 1638 | assert(agentkeepalive.statusChanged === false); 1639 | 1640 | agentkeepalive.requestCount++; 1641 | assert(agentkeepalive.requestCount !== agentkeepalive.requestCountLastCheck); 1642 | assert(agentkeepalive.statusChanged === true); 1643 | assert(agentkeepalive.requestCount === agentkeepalive.requestCountLastCheck); 1644 | assert(agentkeepalive.statusChanged === false); 1645 | }); 1646 | }); 1647 | 1648 | describe('mock idle socket error', () => { 1649 | it('should idle socket emit error event', done => { 1650 | const agent = new HttpAgent(); 1651 | 1652 | const options = { 1653 | host: 'r.cnpmjs.org', 1654 | port: 80, 1655 | path: '/', 1656 | agent, 1657 | }; 1658 | 1659 | const socketKey = agent.getName(options); 1660 | const req = http.get(options, res => { 1661 | let size = 0; 1662 | assert(res.headers.connection === 'keep-alive'); 1663 | res.on('data', chunk => { 1664 | size += chunk.length; 1665 | }); 1666 | res.on('end', () => { 1667 | assert(size > 0); 1668 | assert(Object.keys(agent.sockets).length === 1); 1669 | assert(Object.keys(agent.freeSockets).length === 0); 1670 | process.nextTick(() => { 1671 | assert(agent.freeSockets[socketKey].length === 1); 1672 | setTimeout(() => { 1673 | // agent should catch idle socket error event 1674 | agent.freeSockets[socketKey][0].emit('error', new Error('mock read ECONNRESET')); 1675 | 1676 | setTimeout(() => { 1677 | // error socket should be destroy and remove 1678 | assert(Object.keys(agent.freeSockets).length === 0); 1679 | done(); 1680 | }, 10); 1681 | }, 10); 1682 | }); 1683 | }); 1684 | res.resume(); 1685 | }); 1686 | req.on('error', done); 1687 | }); 1688 | }); 1689 | 1690 | describe('options.socketActiveTTL', () => { 1691 | it('should expire on free socket timeout when it is out of ttl', done => { 1692 | const agent = new HttpAgent({ 1693 | keepAlive: true, 1694 | maxSockets: 5, 1695 | maxFreeSockets: 5, 1696 | timeout: 30000, 1697 | freeSocketKeepAliveTimeout: 5000, 1698 | socketActiveTTL: 100, 1699 | }); 1700 | const req1 = http.get({ 1701 | agent, 1702 | port, 1703 | path: '/', 1704 | }, res => { 1705 | assert(res.statusCode === 200); 1706 | res.resume(); 1707 | res.on('end', () => { 1708 | const socket1 = req1.socket; 1709 | const firstCreatedTime = socket1[SOCKET_CREATED_TIME]; 1710 | assert(firstCreatedTime && typeof firstCreatedTime === 'number'); 1711 | setTimeout(() => { 1712 | const req2 = http.get({ 1713 | agent, 1714 | port, 1715 | path: '/', 1716 | }, res => { 1717 | assert(res.statusCode === 200); 1718 | res.resume(); 1719 | res.on('end', () => { 1720 | assert(req2.socket !== socket1); 1721 | const currentCreatedTime = req2.socket[SOCKET_CREATED_TIME]; 1722 | assert(currentCreatedTime && typeof currentCreatedTime === 'number'); 1723 | assert(firstCreatedTime < currentCreatedTime); 1724 | done(); 1725 | }); 1726 | }); 1727 | }, 200); 1728 | }); 1729 | }); 1730 | }); 1731 | 1732 | it('should expire on socket reuse detect when it is out of ttl', done => { 1733 | const agent = new HttpAgent({ 1734 | keepAlive: true, 1735 | socketActiveTTL: 10, 1736 | }); 1737 | const req1 = http.get({ 1738 | agent, 1739 | port, 1740 | path: '/?timeout=20', 1741 | }, res => { 1742 | const socket1 = req1.socket; 1743 | const firstCreatedTime = socket1[SOCKET_CREATED_TIME]; 1744 | assert(firstCreatedTime && typeof firstCreatedTime === 'number'); 1745 | assert(res.statusCode === 200); 1746 | res.resume(); 1747 | res.on('end', () => { 1748 | setImmediate(() => { 1749 | const req2 = http.get({ 1750 | agent, 1751 | port, 1752 | path: '/', 1753 | }, res => { 1754 | // not the same socket 1755 | assert(req2.socket[SOCKET_NAME] !== socket1[SOCKET_NAME]); 1756 | const currentCreatedTime = req2.socket[SOCKET_CREATED_TIME]; 1757 | assert(currentCreatedTime && typeof currentCreatedTime === 'number'); 1758 | assert(firstCreatedTime < currentCreatedTime); 1759 | assert(res.statusCode === 200); 1760 | res.resume(); 1761 | res.on('end', done); 1762 | }); 1763 | }); 1764 | }); 1765 | }); 1766 | }); 1767 | 1768 | it('should not expire active socket when it is in ttl', done => { 1769 | const agent = new HttpAgent({ 1770 | socketActiveTTL: 1000, 1771 | }); 1772 | const req1 = http.get({ 1773 | agent, 1774 | port, 1775 | path: '/', 1776 | }, res => { 1777 | const socket1 = req1.socket; 1778 | const firstCreatedTime = socket1[SOCKET_CREATED_TIME]; 1779 | assert(firstCreatedTime && typeof firstCreatedTime === 'number'); 1780 | assert(res.statusCode === 200); 1781 | res.resume(); 1782 | res.on('end', () => { 1783 | setTimeout(function() { 1784 | const timeout = socket1.timeout || socket1._idleTimeout; 1785 | assert(timeout <= 1000); 1786 | const req2 = http.get({ 1787 | agent, 1788 | port, 1789 | path: '/', 1790 | }, res => { 1791 | assert(res.statusCode === 200); 1792 | res.resume(); 1793 | res.on('end', () => { 1794 | assert(req2.socket[SOCKET_NAME] === socket1[SOCKET_NAME]); 1795 | const currentCreatedTime = req2.socket[SOCKET_CREATED_TIME]; 1796 | assert(currentCreatedTime && typeof currentCreatedTime === 'number'); 1797 | assert(firstCreatedTime === currentCreatedTime); 1798 | done(); 1799 | }); 1800 | }); 1801 | }, 100); 1802 | }); 1803 | }); 1804 | }); 1805 | 1806 | it('should TTL diff > freeSocketTimeout', done => { 1807 | const agent = new HttpAgent({ 1808 | freeSocketTimeout: 500, 1809 | socketActiveTTL: 1000, 1810 | }); 1811 | const req1 = http.get({ 1812 | agent, 1813 | port, 1814 | path: '/', 1815 | }, res => { 1816 | const socket1 = req1.socket; 1817 | const firstCreatedTime = socket1[SOCKET_CREATED_TIME]; 1818 | assert(firstCreatedTime && typeof firstCreatedTime === 'number'); 1819 | assert(res.statusCode === 200); 1820 | res.resume(); 1821 | res.on('end', () => { 1822 | setTimeout(function() { 1823 | const timeout = socket1.timeout || socket1._idleTimeout; 1824 | assert(timeout === 500); 1825 | const req2 = http.get({ 1826 | agent, 1827 | port, 1828 | path: '/', 1829 | }, res => { 1830 | assert(res.statusCode === 200); 1831 | res.resume(); 1832 | res.on('end', () => { 1833 | assert(req2.socket[SOCKET_NAME] === socket1[SOCKET_NAME]); 1834 | const currentCreatedTime = req2.socket[SOCKET_CREATED_TIME]; 1835 | assert(currentCreatedTime && typeof currentCreatedTime === 'number'); 1836 | assert(firstCreatedTime === currentCreatedTime); 1837 | done(); 1838 | }); 1839 | }); 1840 | }, 100); 1841 | }); 1842 | }); 1843 | }); 1844 | }); 1845 | }); 1846 | -------------------------------------------------------------------------------- /test/https_agent.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const https = require('https'); 4 | const urlparse = require('url').parse; 5 | const fs = require('fs'); 6 | const assert = require('assert'); 7 | const HttpsAgent = require('..').HttpsAgent; 8 | 9 | describe('test/https_agent.test.js', () => { 10 | let app = null; 11 | let port = null; 12 | const agentkeepalive = new HttpsAgent({ 13 | freeSocketTimeout: 1000, 14 | timeout: 2000, 15 | maxSockets: 5, 16 | maxFreeSockets: 5, 17 | }); 18 | before(done => { 19 | app = https.createServer({ 20 | key: fs.readFileSync(__dirname + '/fixtures/agenttest-key.pem'), 21 | cert: fs.readFileSync(__dirname + '/fixtures/agenttest-cert.pem'), 22 | }, (req, res) => { 23 | req.resume(); 24 | if (req.url === '/error') { 25 | res.destroy(); 26 | return; 27 | } else if (req.url === '/hang') { 28 | console.log('[new https request] %s %s', req.method, req.url); 29 | // Wait forever. 30 | return; 31 | } 32 | const info = urlparse(req.url, true); 33 | if (info.query.timeout) { 34 | console.log('[new https request] %s %s, query %j', req.method, req.url, info.query); 35 | setTimeout(() => { 36 | res.writeHeader(200, { 37 | 'Content-Length': `${info.query.timeout.length}`, 38 | }); 39 | res.end(info.query.timeout); 40 | }, parseInt(info.query.timeout)); 41 | return; 42 | } 43 | res.end(JSON.stringify({ 44 | info, 45 | url: req.url, 46 | headers: req.headers, 47 | remotePort: req.socket.remotePort, 48 | })); 49 | }); 50 | app.listen(0, () => { 51 | port = app.address().port; 52 | done(); 53 | }); 54 | }); 55 | 56 | after(done => { 57 | setTimeout(done, 1500); 58 | }); 59 | 60 | it('should GET / success with 200 status', done => { 61 | https.get({ 62 | agent: agentkeepalive, 63 | port, 64 | path: '/', 65 | ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'), 66 | rejectUnauthorized: false, 67 | }, res => { 68 | assert(res.statusCode === 200); 69 | res.resume(); 70 | res.on('end', () => { 71 | assert(Object.keys(agentkeepalive.sockets).length === 1); 72 | assert(Object.keys(agentkeepalive.freeSockets).length === 0); 73 | setImmediate(() => { 74 | assert(Object.keys(agentkeepalive.sockets).length === 0); 75 | assert(Object.keys(agentkeepalive.freeSockets).length === 1); 76 | done(); 77 | }); 78 | }); 79 | }); 80 | assert(Object.keys(agentkeepalive.sockets).length === 1); 81 | assert(Object.keys(agentkeepalive.freeSockets).length === 0); 82 | }); 83 | 84 | it('should req handle custom timeout error', done => { 85 | const req = https.get({ 86 | agent: agentkeepalive, 87 | port, 88 | path: '/?timeout=100', 89 | ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'), 90 | timeout: 50, 91 | rejectUnauthorized: false, 92 | }, res => { 93 | console.log(res.statusCode, res.headers); 94 | res.resume(); 95 | res.on('end', () => { 96 | done(new Error('should not run this')); 97 | }); 98 | }).on('error', err => { 99 | assert(err); 100 | assert(err.message === 'socket hang up'); 101 | done(); 102 | }); 103 | 104 | // node 8 don't support options.timeout on http.get 105 | if (process.version.startsWith('v8.')) { 106 | req.setTimeout(50); 107 | } 108 | req.on('timeout', () => { 109 | req.abort(); 110 | }); 111 | }); 112 | 113 | it('should agent handle default timeout error [bugfix for node 8, 10]', done => { 114 | const agent = new HttpsAgent({ 115 | freeSocketTimeout: 1000, 116 | timeout: 50, 117 | maxSockets: 5, 118 | maxFreeSockets: 5, 119 | rejectUnauthorized: false, 120 | }); 121 | https.get({ 122 | agent, 123 | port, 124 | path: '/?timeout=100', 125 | ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'), 126 | }, res => { 127 | console.log(res.statusCode, res.headers); 128 | res.resume(); 129 | res.on('end', () => { 130 | done(new Error('should not run this')); 131 | }); 132 | }).on('error', err => { 133 | assert(err); 134 | assert(err.message === 'Socket timeout'); 135 | done(); 136 | }); 137 | }); 138 | 139 | it('should don\'t set timeout on options.timeout = 0', done => { 140 | const agent = new HttpsAgent({ 141 | freeSocketTimeout: 1000, 142 | timeout: 0, 143 | maxSockets: 5, 144 | maxFreeSockets: 5, 145 | rejectUnauthorized: false, 146 | }); 147 | https.get({ 148 | agent, 149 | port, 150 | path: '/', 151 | ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'), 152 | }, res => { 153 | res.resume(); 154 | res.on('end', done); 155 | }); 156 | }); 157 | 158 | it('should free socket timeout', done => { 159 | https.get({ 160 | agent: agentkeepalive, 161 | port, 162 | path: '/', 163 | ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'), 164 | rejectUnauthorized: false, 165 | }, res => { 166 | assert(res.statusCode === 200); 167 | res.resume(); 168 | res.on('end', () => { 169 | process.nextTick(() => { 170 | assert(Object.keys(agentkeepalive.sockets).length === 0); 171 | assert(Object.keys(agentkeepalive.freeSockets).length === 1); 172 | // wait for timeout 173 | setTimeout(() => { 174 | assert(Object.keys(agentkeepalive.sockets).length === 0); 175 | assert(Object.keys(agentkeepalive.freeSockets).length === 0); 176 | done(); 177 | }, 1500); 178 | }); 179 | }); 180 | }); 181 | assert(Object.keys(agentkeepalive.sockets).length === 1); 182 | }); 183 | 184 | it('should GET / and /foo use the same socket', done => { 185 | const options = { 186 | port, 187 | path: '/', 188 | agent: agentkeepalive, 189 | rejectUnauthorized: false, 190 | }; 191 | let remotePort = null; 192 | https.get(options, res => { 193 | assert(res.statusCode === 200); 194 | let data = null; 195 | res.on('data', chunk => { 196 | data = JSON.parse(chunk); 197 | }); 198 | res.on('end', () => { 199 | assert(data.remotePort > 0); 200 | assert(data.url === '/'); 201 | remotePort = data.remotePort; 202 | 203 | // request again 204 | options.path = '/foo'; 205 | process.nextTick(() => { 206 | https.get(options, res => { 207 | assert(res.statusCode === 200); 208 | let data = null; 209 | res.on('data', chunk => { 210 | data = JSON.parse(chunk); 211 | }); 212 | res.on('end', () => { 213 | assert(data.remotePort === remotePort); 214 | assert(data.url === '/foo'); 215 | process.nextTick(() => { 216 | assert(Object.keys(agentkeepalive.sockets).length === 0); 217 | assert(Object.keys(agentkeepalive.freeSockets).length === 1); 218 | done(); 219 | }); 220 | }); 221 | }); 222 | }); 223 | }); 224 | }); 225 | }); 226 | 227 | describe('request timeout > agent timeout', () => { 228 | it('should use request timeout', done => { 229 | const agent = new HttpsAgent({ 230 | keepAlive: true, 231 | timeout: 2000, 232 | }); 233 | const req = https.get({ 234 | agent, 235 | port, 236 | path: '/?timeout=20000', 237 | timeout: 2500, 238 | rejectUnauthorized: false, 239 | ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'), 240 | }, res => { 241 | console.error(res.statusCode, res.headers); 242 | assert.fail('should not get res here'); 243 | }); 244 | 245 | let isTimeout = false; 246 | req.on('timeout', () => { 247 | isTimeout = true; 248 | req.abort(); 249 | }); 250 | req.on('error', err => { 251 | assert(isTimeout); 252 | assert(err); 253 | assert(err.message === 'socket hang up'); 254 | assert(err.code === 'ECONNRESET'); 255 | done(); 256 | }); 257 | }); 258 | }); 259 | }); 260 | -------------------------------------------------------------------------------- /test/server_timeout.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const http = require('http'); 5 | const HttpAgent = require('..').HttpAgent; 6 | 7 | describe('test/server_timeout.test.js', () => { 8 | let port; 9 | let server; 10 | let timer; 11 | before(done => { 12 | server = http.createServer((req, res) => { 13 | if (server.keepAliveTimeout) { 14 | res.setHeader('Keep-Alive', `timeout=${parseInt(server.keepAliveTimeout / 1000)}`); 15 | } 16 | res.end('Hello World, ' + req.connection.remotePort); 17 | }); 18 | server.on('clientError', (err, socket) => { 19 | socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); 20 | }); 21 | server.keepAliveTimeout = 1000; 22 | server.listen(0, err => { 23 | port = server.address().port; 24 | done(err); 25 | }); 26 | }); 27 | 28 | after(() => { 29 | clearInterval(timer); 30 | }); 31 | 32 | it('should handle Keep-Alive header and not throw reset error', done => { 33 | const keepaliveAgent = new HttpAgent({ 34 | keepAlive: true, 35 | }); 36 | 37 | let count = 0; 38 | function request() { 39 | count++; 40 | const req = http.request({ 41 | method: 'GET', 42 | port, 43 | path: '/', 44 | agent: keepaliveAgent, 45 | }, res => { 46 | assert(res.statusCode === 200); 47 | const chunks = []; 48 | res.on('data', data => { 49 | chunks.push(data); 50 | }); 51 | res.on('end', () => { 52 | const text = Buffer.concat(chunks).toString(); 53 | console.log('[%s] status: %s, text: %s, headers: %j', count, text, res.statusCode, res.headers); 54 | assert(res.headers.connection === 'keep-alive'); 55 | assert(res.headers['keep-alive'] === 'timeout=1'); 56 | const m = /^timeout=(\d+?)/.exec(res.headers['keep-alive']); 57 | if (m) { 58 | const keepAliveTimeout = parseInt(m[1]) * 1000 - 500; 59 | if (keepAliveTimeout > 0) { 60 | req.socket.freeSocketKeepAliveTimeout = keepAliveTimeout; 61 | } 62 | } 63 | if (count > 5) { 64 | done(); 65 | } 66 | }); 67 | }); 68 | req.on('error', err => { 69 | console.error('[%s] error: %s', count, err); 70 | done(err); 71 | }); 72 | req.end(); 73 | } 74 | 75 | timer = setInterval(request, server.keepAliveTimeout); 76 | request(); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/test-ECONNRESET.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const http = require('http'); 5 | const HttpAgent = require('..').HttpAgent; 6 | 7 | const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); 8 | 9 | // https://medium.com/ssense-tech/reduce-networking-errors-in-nodejs-23b4eb9f2d83 10 | describe('test/test-ECONNRESET.test.js', () => { 11 | let port; 12 | let server; 13 | let timer; 14 | before(done => { 15 | server = http.createServer((req, res) => { 16 | res.end('Hello World'); 17 | }); 18 | server.keepAliveTimeout = 1000; 19 | server.listen(0, err => { 20 | port = server.address().port; 21 | done(err); 22 | }); 23 | }); 24 | 25 | after(() => { 26 | clearInterval(timer); 27 | }); 28 | 29 | it('should resolved socket hang up and ECONNRESET errors', done => { 30 | const keepaliveAgent = new HttpAgent({ 31 | keepAlive: true, 32 | freeSocketTimeout: 900, 33 | }); 34 | 35 | function request() { 36 | return new Promise((resolve, reject) => { 37 | const req = http.request({ 38 | method: 'GET', 39 | port, 40 | path: '/', 41 | agent: keepaliveAgent, 42 | }, res => { 43 | const chunks = []; 44 | res.on('data', data => { 45 | chunks.push(data); 46 | }); 47 | res.on('end', () => { 48 | const text = Buffer.concat(chunks).toString(); 49 | resolve(text); 50 | }); 51 | }); 52 | req.on('error', err => { 53 | reject(err); 54 | }); 55 | req.end(); 56 | }); 57 | } 58 | 59 | async function startSendingRequests() { 60 | let successes = 0; 61 | const failures = {}; 62 | 63 | for (let i = 0; i < 10; i++) { 64 | await wait(999); 65 | try { 66 | await request(); 67 | successes++; 68 | } catch (e) { 69 | failures[e.message] = (failures[e.message] || 0) + 1; 70 | } 71 | } 72 | return { successes, failures }; 73 | } 74 | 75 | startSendingRequests().then(({ successes, failures }) => { 76 | assert(Object.keys(failures).length === 0); 77 | assert(successes === 10); 78 | done(); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/test-http-agent-maxsockets-regress-4050.test.js: -------------------------------------------------------------------------------- 1 | // http: remove excess calls to removeSocket 2 | // https://github.com/nodejs/node/commit/6e11e220814e469cbbbe91b895362f6f11311c08 3 | 4 | 'use strict'; 5 | 6 | const assert = require('assert'); 7 | const http = require('http'); 8 | 9 | describe('test/test-http-agent-maxsockets-regress-4050.test.js', () => { 10 | it('should keep active sockets <= MAX_SOCKETS when all requests abort', done => { 11 | const MAX_SOCKETS = 2; 12 | 13 | const agent = new http.Agent({ 14 | keepAlive: true, 15 | keepAliveMsecs: 1000, 16 | maxSockets: MAX_SOCKETS, 17 | maxFreeSockets: 2, 18 | }); 19 | 20 | const server = http.createServer((req, res) => { 21 | res.end('hello world'); 22 | }); 23 | 24 | let port; 25 | const num_requests = 6; 26 | let finished = 0; 27 | 28 | function get(path) { 29 | const req = http.get({ 30 | host: 'localhost', 31 | port, 32 | agent, 33 | path, 34 | }, () => { 35 | req.abort(); 36 | const sockets = agent.sockets[Object.keys(agent.sockets)[0]]; 37 | assert(sockets.length <= MAX_SOCKETS); 38 | if (++finished === num_requests) { 39 | server.close(); 40 | done(); 41 | } 42 | }); 43 | } 44 | 45 | server.listen(0, () => { 46 | port = server.address().port; 47 | for (let i = 0; i < num_requests; i++) { 48 | get('/' + i); 49 | } 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/test-https-agent-session-eviction.test.js: -------------------------------------------------------------------------------- 1 | // https://github.com/indutny/io.js/blob/b9b79fdbed704cb647700d799b94f15e88124763/test/parallel/test-https-agent-session-eviction.js 2 | 3 | 'use strict'; 4 | 5 | const https = require('https'); 6 | const assert = require('assert'); 7 | const fs = require('fs'); 8 | const constants = require('constants'); 9 | const HttpsAgent = require('..').HttpsAgent; 10 | 11 | describe('test/test-https-agent-session-eviction.test.js', () => { 12 | let port; 13 | let server; 14 | const httpsAgent = new HttpsAgent({ 15 | keepAlive: true, 16 | }); 17 | const options = { 18 | key: fs.readFileSync(__dirname + '/fixtures/agenttest-key.pem'), 19 | cert: fs.readFileSync(__dirname + '/fixtures/agenttest-cert.pem'), 20 | secureOptions: constants.SSL_OP_NO_TICKET, 21 | }; 22 | 23 | before(done => { 24 | // Create TLS1.2 server 25 | server = https.createServer(options, (req, res) => { 26 | res.end('ohai'); 27 | }); 28 | server.listen(0, () => { 29 | port = server.address().port; 30 | done(); 31 | }); 32 | }); 33 | 34 | it('should evict cached sessions on error', done => { 35 | get(); 36 | // Do request and let agent cache the session 37 | function get() { 38 | const req = https.request({ 39 | port, 40 | rejectUnauthorized: false, 41 | agent: httpsAgent, 42 | }, res => { 43 | console.log('first request done, %j', res.headers); 44 | res.resume(); 45 | res.on('end', () => { 46 | process.nextTick(() => { 47 | const name = Object.keys(httpsAgent.freeSockets); 48 | const socket = httpsAgent.freeSockets[name][0]; 49 | socket.on('close', err => { 50 | if (socket.destroyed) return; 51 | assert.equal(err.message, 'mock close error'); 52 | socket.destroy(); 53 | done(); 54 | }); 55 | socket.emit('close', new Error('mock close error')); 56 | }); 57 | }); 58 | }); 59 | req.end(); 60 | } 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/test-https-ipv6.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const https = require('https'); 4 | const assert = require('assert'); 5 | const fs = require('fs'); 6 | const constants = require('constants'); 7 | const HttpsAgent = require('..').HttpsAgent; 8 | const os = require('os'); 9 | 10 | function isIPv6Available() { 11 | const networkInterfaces = os.networkInterfaces(); 12 | return !!Object.keys(networkInterfaces).find(ifName => { 13 | const addresses = networkInterfaces[ifName]; 14 | return !!addresses.find(addr => addr.family === 'IPv6'); 15 | }); 16 | } 17 | 18 | describe('test/test-ipv6.test.js', () => { 19 | let port; 20 | let server; 21 | const httpsAgent = new HttpsAgent({ 22 | keepAlive: true, 23 | }); 24 | const options = { 25 | key: fs.readFileSync(__dirname + '/fixtures/agenttest-key.pem'), 26 | cert: fs.readFileSync(__dirname + '/fixtures/agenttest-cert.pem'), 27 | secureOptions: constants.SSL_OP_NO_TICKET, 28 | }; 29 | 30 | before(function(done) { 31 | if (!isIPv6Available()) { 32 | this.skip(); 33 | } 34 | 35 | // Create TLS1.2 server 36 | server = https.createServer(options, (req, res) => { 37 | res.end('ohai'); 38 | }); 39 | server.listen(0, () => { 40 | port = server.address().port; 41 | done(); 42 | }); 43 | }); 44 | 45 | it('should GET / success with 200 status from ::1', function(done) { 46 | const m = process.version.match(/^v(\d+)\.(\d+)/); 47 | const major = parseInt(m[1]); 48 | const minor = parseInt(m[2]); 49 | if (major < 8 || (major === 8 && minor < 10) || (major === 9 && minor < 1)) { 50 | // This only works in node-versions with the fix for 51 | // https://github.com/nodejs/node/issues/14736 included. 52 | this.skip(); 53 | } 54 | 55 | https.get({ 56 | agent: httpsAgent, 57 | hostname: '::1', 58 | port, 59 | path: '/', 60 | ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'), 61 | rejectUnauthorized: false, 62 | }, res => { 63 | assert(res.statusCode === 200); 64 | res.resume(); 65 | res.on('end', () => { 66 | process.nextTick(() => { 67 | assert(Object.keys(httpsAgent.sockets).length === 0); 68 | assert(Object.keys(httpsAgent.freeSockets).length === 1); 69 | done(); 70 | }); 71 | }); 72 | }); 73 | assert(Object.keys(httpsAgent.sockets).length === 1); 74 | }); 75 | 76 | it('should not crash with invalid host-header', done => { 77 | https.get({ 78 | agent: httpsAgent, 79 | hostname: '::1', 80 | port, 81 | path: '/', 82 | headers: { 83 | host: '[::1:80', 84 | }, 85 | rejectUnauthorized: false, 86 | }, () => { 87 | done(); 88 | }); 89 | }); 90 | 91 | }); 92 | -------------------------------------------------------------------------------- /test/ts.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const coffee = require('coffee'); 5 | 6 | describe('test/ts.test.js', () => { 7 | it('should compile ts and run without error', async () => { 8 | await coffee.fork( 9 | require.resolve('typescript/bin/tsc'), 10 | [ '-p', path.resolve(__dirname, './fixtures/ts/tsconfig.json') ] 11 | ) 12 | .debug() 13 | .expect('code', 0) 14 | .end(); 15 | 16 | await coffee.fork(path.resolve(__dirname, './fixtures/ts/index.js')) 17 | // .debug() 18 | .expect('code', 0) 19 | .end(); 20 | }); 21 | }); 22 | --------------------------------------------------------------------------------