agent.js 4.18 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
/**
 * 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;
}