You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. 'use strict';
  2. require('./patch-core');
  3. const inherits = require('util').inherits;
  4. const promisify = require('es6-promisify');
  5. const EventEmitter = require('events').EventEmitter;
  6. module.exports = Agent;
  7. function isAgent(v) {
  8. return v && typeof v.addRequest === 'function';
  9. }
  10. /**
  11. * Base `http.Agent` implementation.
  12. * No pooling/keep-alive is implemented by default.
  13. *
  14. * @param {Function} callback
  15. * @api public
  16. */
  17. function Agent(callback, _opts) {
  18. if (!(this instanceof Agent)) {
  19. return new Agent(callback, _opts);
  20. }
  21. EventEmitter.call(this);
  22. // The callback gets promisified if it has 3 parameters
  23. // (i.e. it has a callback function) lazily
  24. this._promisifiedCallback = false;
  25. let opts = _opts;
  26. if ('function' === typeof callback) {
  27. this.callback = callback;
  28. } else if (callback) {
  29. opts = callback;
  30. }
  31. // timeout for the socket to be returned from the callback
  32. this.timeout = (opts && opts.timeout) || null;
  33. this.options = opts;
  34. }
  35. inherits(Agent, EventEmitter);
  36. /**
  37. * Override this function in your subclass!
  38. */
  39. Agent.prototype.callback = function callback(req, opts) {
  40. throw new Error(
  41. '"agent-base" has no default implementation, you must subclass and override `callback()`'
  42. );
  43. };
  44. /**
  45. * Called by node-core's "_http_client.js" module when creating
  46. * a new HTTP request with this Agent instance.
  47. *
  48. * @api public
  49. */
  50. Agent.prototype.addRequest = function addRequest(req, _opts) {
  51. const ownOpts = Object.assign({}, _opts);
  52. // Set default `host` for HTTP to localhost
  53. if (null == ownOpts.host) {
  54. ownOpts.host = 'localhost';
  55. }
  56. // Set default `port` for HTTP if none was explicitly specified
  57. if (null == ownOpts.port) {
  58. ownOpts.port = ownOpts.secureEndpoint ? 443 : 80;
  59. }
  60. const opts = Object.assign({}, this.options, ownOpts);
  61. if (opts.host && opts.path) {
  62. // If both a `host` and `path` are specified then it's most likely the
  63. // result of a `url.parse()` call... we need to remove the `path` portion so
  64. // that `net.connect()` doesn't attempt to open that as a unix socket file.
  65. delete opts.path;
  66. }
  67. delete opts.agent;
  68. delete opts.hostname;
  69. delete opts._defaultAgent;
  70. delete opts.defaultPort;
  71. delete opts.createConnection;
  72. // Hint to use "Connection: close"
  73. // XXX: non-documented `http` module API :(
  74. req._last = true;
  75. req.shouldKeepAlive = false;
  76. // Create the `stream.Duplex` instance
  77. let timeout;
  78. let timedOut = false;
  79. const timeoutMs = this.timeout;
  80. const freeSocket = this.freeSocket;
  81. function onerror(err) {
  82. if (req._hadError) return;
  83. req.emit('error', err);
  84. // For Safety. Some additional errors might fire later on
  85. // and we need to make sure we don't double-fire the error event.
  86. req._hadError = true;
  87. }
  88. function ontimeout() {
  89. timeout = null;
  90. timedOut = true;
  91. const err = new Error(
  92. 'A "socket" was not created for HTTP request before ' + timeoutMs + 'ms'
  93. );
  94. err.code = 'ETIMEOUT';
  95. onerror(err);
  96. }
  97. function callbackError(err) {
  98. if (timedOut) return;
  99. if (timeout != null) {
  100. clearTimeout(timeout);
  101. timeout = null;
  102. }
  103. onerror(err);
  104. }
  105. function onsocket(socket) {
  106. if (timedOut) return;
  107. if (timeout != null) {
  108. clearTimeout(timeout);
  109. timeout = null;
  110. }
  111. if (isAgent(socket)) {
  112. // `socket` is actually an http.Agent instance, so relinquish
  113. // responsibility for this `req` to the Agent from here on
  114. socket.addRequest(req, opts);
  115. } else if (socket) {
  116. function onfree() {
  117. freeSocket(socket, opts);
  118. }
  119. socket.on('free', onfree);
  120. req.onSocket(socket);
  121. } else {
  122. const err = new Error(
  123. 'no Duplex stream was returned to agent-base for `' + req.method + ' ' + req.path + '`'
  124. );
  125. onerror(err);
  126. }
  127. }
  128. if (!this._promisifiedCallback && this.callback.length >= 3) {
  129. // Legacy callback function - convert to a Promise
  130. this.callback = promisify(this.callback, this);
  131. this._promisifiedCallback = true;
  132. }
  133. if (timeoutMs > 0) {
  134. timeout = setTimeout(ontimeout, timeoutMs);
  135. }
  136. try {
  137. Promise.resolve(this.callback(req, opts)).then(onsocket, callbackError);
  138. } catch (err) {
  139. Promise.reject(err).catch(callbackError);
  140. }
  141. };
  142. Agent.prototype.freeSocket = function freeSocket(socket, opts) {
  143. // TODO reuse sockets
  144. socket.destroy();
  145. };