index.js
1 'use strict'; 2 3 /** 4 * Module dependencies. 5 */ 6 7 var url = require('url'); 8 var LRU = require('lru-cache'); 9 var Agent = require('agent-base'); 10 var inherits = require('util').inherits; 11 var debug = require('debug')('proxy-agent'); 12 var getProxyForUrl = require('proxy-from-env').getProxyForUrl; 13 14 var http = require('http'); 15 var https = require('https'); 16 var PacProxyAgent = require('pac-proxy-agent'); 17 var HttpProxyAgent = require('http-proxy-agent'); 18 var HttpsProxyAgent = require('https-proxy-agent'); 19 var SocksProxyAgent = require('socks-proxy-agent'); 20 21 /** 22 * Module exports. 23 */ 24 25 exports = module.exports = ProxyAgent; 26 27 /** 28 * Number of `http.Agent` instances to cache. 29 * 30 * This value was arbitrarily chosen... a better 31 * value could be conceived with some benchmarks. 32 */ 33 34 var cacheSize = 20; 35 36 /** 37 * Cache for `http.Agent` instances. 38 */ 39 40 exports.cache = new LRU(cacheSize); 41 42 /** 43 * Built-in proxy types. 44 */ 45 46 exports.proxies = Object.create(null); 47 exports.proxies.http = httpOrHttpsProxy; 48 exports.proxies.https = httpOrHttpsProxy; 49 exports.proxies.socks = SocksProxyAgent; 50 exports.proxies.socks4 = SocksProxyAgent; 51 exports.proxies.socks4a = SocksProxyAgent; 52 exports.proxies.socks5 = SocksProxyAgent; 53 exports.proxies.socks5h = SocksProxyAgent; 54 55 PacProxyAgent.protocols.forEach(function (protocol) { 56 exports.proxies['pac+' + protocol] = PacProxyAgent; 57 }); 58 59 function httpOrHttps(opts, secureEndpoint) { 60 if (secureEndpoint) { 61 // HTTPS 62 return https.globalAgent; 63 } else { 64 // HTTP 65 return http.globalAgent; 66 } 67 } 68 69 function httpOrHttpsProxy(opts, secureEndpoint) { 70 if (secureEndpoint) { 71 // HTTPS 72 return new HttpsProxyAgent(opts); 73 } else { 74 // HTTP 75 return new HttpProxyAgent(opts); 76 } 77 } 78 79 function mapOptsToProxy(opts) { 80 // NO_PROXY case 81 if (!opts) { 82 return { 83 uri: 'no proxy', 84 fn: httpOrHttps 85 }; 86 } 87 88 if ('string' == typeof opts) opts = url.parse(opts); 89 90 var proxies; 91 if (opts.proxies) { 92 proxies = Object.assign({}, exports.proxies, opts.proxies); 93 } else { 94 proxies = exports.proxies; 95 } 96 97 // get the requested proxy "protocol" 98 var protocol = opts.protocol; 99 if (!protocol) { 100 throw new TypeError('You must specify a "protocol" for the ' + 101 'proxy type (' + Object.keys(proxies).join(', ') + ')'); 102 } 103 104 // strip the trailing ":" if present 105 if (':' == protocol[protocol.length - 1]) { 106 protocol = protocol.substring(0, protocol.length - 1); 107 } 108 109 // get the proxy `http.Agent` creation function 110 var proxyFn = proxies[protocol]; 111 if ('function' != typeof proxyFn) { 112 throw new TypeError('unsupported proxy protocol: "' + protocol + '"'); 113 } 114 115 // format the proxy info back into a URI, since an opts object 116 // could have been passed in originally. This generated URI is used 117 // as part of the "key" for the LRU cache 118 return { 119 opts: opts, 120 uri: url.format({ 121 protocol: protocol + ':', 122 slashes: true, 123 auth: opts.auth, 124 hostname: opts.hostname || opts.host, 125 port: opts.port 126 }), 127 fn: proxyFn, 128 } 129 } 130 131 /** 132 * Attempts to get an `http.Agent` instance based off of the given proxy URI 133 * information, and the `secure` flag. 134 * 135 * An LRU cache is used, to prevent unnecessary creation of proxy 136 * `http.Agent` instances. 137 * 138 * @param {String} uri proxy url 139 * @param {Boolean} secure true if this is for an HTTPS request, false for HTTP 140 * @return {http.Agent} 141 * @api public 142 */ 143 144 function ProxyAgent (opts) { 145 if (!(this instanceof ProxyAgent)) return new ProxyAgent(opts); 146 debug('creating new ProxyAgent instance: %o', opts); 147 Agent.call(this); 148 149 if (opts) { 150 var proxy = mapOptsToProxy(opts); 151 this.proxy = proxy.opts; 152 this.proxyUri = proxy.uri; 153 this.proxyFn = proxy.fn; 154 } 155 } 156 inherits(ProxyAgent, Agent); 157 158 /** 159 * 160 */ 161 162 ProxyAgent.prototype.callback = function(req, opts, fn) { 163 var proxyOpts = this.proxy; 164 var proxyUri = this.proxyUri; 165 var proxyFn = this.proxyFn; 166 167 // if we did not instantiate with a proxy, set one per request 168 if (!proxyOpts) { 169 var urlOpts = getProxyForUrl(opts); 170 var proxy = mapOptsToProxy(urlOpts, opts); 171 proxyOpts = proxy.opts; 172 proxyUri = proxy.uri; 173 proxyFn = proxy.fn; 174 } 175 176 // create the "key" for the LRU cache 177 var key = proxyUri; 178 if (opts.secureEndpoint) key += ' secure'; 179 180 // attempt to get a cached `http.Agent` instance first 181 var agent = exports.cache.get(key); 182 if (!agent) { 183 // get an `http.Agent` instance from protocol-specific agent function 184 agent = proxyFn(proxyOpts, opts.secureEndpoint); 185 if (agent) { 186 exports.cache.set(key, agent); 187 } 188 } else { 189 debug('cache hit with key: %o', key); 190 } 191 192 if (!proxyOpts) { 193 agent.addRequest(req, opts); 194 } else { 195 // XXX: agent.callback() is an agent-base-ism 196 agent.callback(req, opts) 197 .then(function(socket) { fn(null, socket); }) 198 .catch(function(error) { fn(error); }); 199 } 200 }