'use strict'; require('./patch-core'); const inherits = require('util').inherits; const promisify = require('es6-promisify'); const EventEmitter = require('events').EventEmitter; module.exports = Agent; function isAgent(v) { return v && typeof v.addRequest === 'function'; } /** * Base `http.Agent` implementation. * No pooling/keep-alive is implemented by default. * * @param {Function} callback * @api public */ function Agent(callback, _opts) { if (!(this instanceof Agent)) { return new Agent(callback, _opts); } EventEmitter.call(this); // The callback gets promisified if it has 3 parameters // (i.e. it has a callback function) lazily this._promisifiedCallback = false; let opts = _opts; if ('function' === typeof callback) { this.callback = callback; } else if (callback) { opts = callback; } // timeout for the socket to be returned from the callback this.timeout = (opts && opts.timeout) || null; this.options = opts; } inherits(Agent, EventEmitter); /** * Override this function in your subclass! */ Agent.prototype.callback = function callback(req, opts) { throw new Error( '"agent-base" has no default implementation, you must subclass and override `callback()`' ); }; /** * Called by node-core's "_http_client.js" module when creating * a new HTTP request with this Agent instance. * * @api public */ Agent.prototype.addRequest = function addRequest(req, _opts) { const ownOpts = Object.assign({}, _opts); // Set default `host` for HTTP to localhost if (null == ownOpts.host) { ownOpts.host = 'localhost'; } // Set default `port` for HTTP if none was explicitly specified if (null == ownOpts.port) { ownOpts.port = ownOpts.secureEndpoint ? 443 : 80; } const opts = Object.assign({}, this.options, ownOpts); if (opts.host && opts.path) { // If both a `host` and `path` are specified then it's most likely the // result of a `url.parse()` call... we need to remove the `path` portion so // that `net.connect()` doesn't attempt to open that as a unix socket file. delete opts.path; } delete opts.agent; delete opts.hostname; delete opts._defaultAgent; delete opts.defaultPort; delete opts.createConnection; // Hint to use "Connection: close" // XXX: non-documented `http` module API :( req._last = true; req.shouldKeepAlive = false; // Create the `stream.Duplex` instance let timeout; let timedOut = false; const timeoutMs = this.timeout; const freeSocket = this.freeSocket; function onerror(err) { if (req._hadError) return; req.emit('error', err); // For Safety. Some additional errors might fire later on // and we need to make sure we don't double-fire the error event. req._hadError = true; } function ontimeout() { timeout = null; timedOut = true; const err = new Error( 'A "socket" was not created for HTTP request before ' + timeoutMs + 'ms' ); err.code = 'ETIMEOUT'; onerror(err); } function callbackError(err) { if (timedOut) return; if (timeout != null) { clearTimeout(timeout); timeout = null; } onerror(err); } function onsocket(socket) { if (timedOut) return; if (timeout != null) { clearTimeout(timeout); timeout = null; } if (isAgent(socket)) { // `socket` is actually an http.Agent instance, so relinquish // responsibility for this `req` to the Agent from here on socket.addRequest(req, opts); } else if (socket) { function onfree() { freeSocket(socket, opts); } socket.on('free', onfree); req.onSocket(socket); } else { const err = new Error( 'no Duplex stream was returned to agent-base for `' + req.method + ' ' + req.path + '`' ); onerror(err); } } if (!this._promisifiedCallback && this.callback.length >= 3) { // Legacy callback function - convert to a Promise this.callback = promisify(this.callback, this); this._promisifiedCallback = true; } if (timeoutMs > 0) { timeout = setTimeout(ontimeout, timeoutMs); } try { Promise.resolve(this.callback(req, opts)).then(onsocket, callbackError); } catch (err) { Promise.reject(err).catch(callbackError); } }; Agent.prototype.freeSocket = function freeSocket(socket, opts) { // TODO reuse sockets socket.destroy(); };