| /** | |
|  * refer: | |
|  *   * @atimb "Real keep-alive HTTP agent": https://gist.github.com/2963672 | |
|  *   * https://github.com/joyent/node/blob/master/lib/http.js | |
|  *   * https://github.com/joyent/node/blob/master/lib/https.js | |
|  *   * https://github.com/joyent/node/blob/master/lib/_http_agent.js | |
|  */ | |
| 
 | |
| 'use strict'; | |
| 
 | |
| const OriginalAgent = require('./_http_agent').Agent; | |
| const ms = require('humanize-ms'); | |
| 
 | |
| class Agent extends OriginalAgent { | |
|   constructor(options) { | |
|     options = options || {}; | |
|     options.keepAlive = options.keepAlive !== false; | |
|     // default is keep-alive and 15s free socket timeout | |
|     if (options.freeSocketKeepAliveTimeout === undefined) { | |
|       options.freeSocketKeepAliveTimeout = 15000; | |
|     } | |
|     // Legacy API: keepAliveTimeout should be rename to `freeSocketKeepAliveTimeout` | |
|     if (options.keepAliveTimeout) { | |
|       options.freeSocketKeepAliveTimeout = options.keepAliveTimeout; | |
|     } | |
|     options.freeSocketKeepAliveTimeout = ms(options.freeSocketKeepAliveTimeout); | |
| 
 | |
|     // Sets the socket to timeout after timeout milliseconds of inactivity on the socket. | |
|     // By default is double free socket keepalive timeout. | |
|     if (options.timeout === undefined) { | |
|       options.timeout = options.freeSocketKeepAliveTimeout * 2; | |
|       // make sure socket default inactivity timeout >= 30s | |
|       if (options.timeout < 30000) { | |
|         options.timeout = 30000; | |
|       } | |
|     } | |
|     options.timeout = ms(options.timeout); | |
| 
 | |
|     super(options); | |
| 
 | |
|     this.createSocketCount = 0; | |
|     this.createSocketCountLastCheck = 0; | |
| 
 | |
|     this.createSocketErrorCount = 0; | |
|     this.createSocketErrorCountLastCheck = 0; | |
| 
 | |
|     this.closeSocketCount = 0; | |
|     this.closeSocketCountLastCheck = 0; | |
| 
 | |
|     // socket error event count | |
|     this.errorSocketCount = 0; | |
|     this.errorSocketCountLastCheck = 0; | |
| 
 | |
|     this.requestCount = 0; | |
|     this.requestCountLastCheck = 0; | |
| 
 | |
|     this.timeoutSocketCount = 0; | |
|     this.timeoutSocketCountLastCheck = 0; | |
| 
 | |
|     this.on('free', s => { | |
|       this.requestCount++; | |
|       // last enter free queue timestamp | |
|       s.lastFreeTime = Date.now(); | |
|     }); | |
|     this.on('timeout', () => { | |
|       this.timeoutSocketCount++; | |
|     }); | |
|     this.on('close', () => { | |
|       this.closeSocketCount++; | |
|     }); | |
|     this.on('error', () => { | |
|       this.errorSocketCount++; | |
|     }); | |
|   } | |
| 
 | |
|   createSocket(req, options, cb) { | |
|     super.createSocket(req, options, (err, socket) => { | |
|       if (err) { | |
|         this.createSocketErrorCount++; | |
|         return cb(err); | |
|       } | |
|       if (this.keepAlive) { | |
|         // Disable Nagle's algorithm: http://blog.caustik.com/2012/04/08/scaling-node-js-to-100k-concurrent-connections/ | |
|         // https://fengmk2.com/benchmark/nagle-algorithm-delayed-ack-mock.html | |
|         socket.setNoDelay(true); | |
|       } | |
|       this.createSocketCount++; | |
|       cb(null, socket); | |
|     }); | |
|   } | |
| 
 | |
|   get statusChanged() { | |
|     const changed = this.createSocketCount !== this.createSocketCountLastCheck || | |
|       this.createSocketErrorCount !== this.createSocketErrorCountLastCheck || | |
|       this.closeSocketCount !== this.closeSocketCountLastCheck || | |
|       this.errorSocketCount !== this.errorSocketCountLastCheck || | |
|       this.timeoutSocketCount !== this.timeoutSocketCountLastCheck || | |
|       this.requestCount !== this.requestCountLastCheck; | |
|     if (changed) { | |
|       this.createSocketCountLastCheck = this.createSocketCount; | |
|       this.createSocketErrorCountLastCheck = this.createSocketErrorCount; | |
|       this.closeSocketCountLastCheck = this.closeSocketCount; | |
|       this.errorSocketCountLastCheck = this.errorSocketCount; | |
|       this.timeoutSocketCountLastCheck = this.timeoutSocketCount; | |
|       this.requestCountLastCheck = this.requestCount; | |
|     } | |
|     return changed; | |
|   } | |
| 
 | |
|   getCurrentStatus() { | |
|     return { | |
|       createSocketCount: this.createSocketCount, | |
|       createSocketErrorCount: this.createSocketErrorCount, | |
|       closeSocketCount: this.closeSocketCount, | |
|       errorSocketCount: this.errorSocketCount, | |
|       timeoutSocketCount: this.timeoutSocketCount, | |
|       requestCount: this.requestCount, | |
|       freeSockets: inspect(this.freeSockets), | |
|       sockets: inspect(this.sockets), | |
|       requests: inspect(this.requests), | |
|     }; | |
|   } | |
| } | |
| 
 | |
| module.exports = Agent; | |
| 
 | |
| function inspect(obj) { | |
|   const res = {}; | |
|   for (const key in obj) { | |
|     res[key] = obj[key].length; | |
|   } | |
|   return res; | |
| }
 |