| '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(); |
| }; |