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