diff --git a/index.js b/index.js index 3f037c4..8d50c5b 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,10 @@ var LRU = require('lru-cache'); var Agent = require('agent-base'); var inherits = require('util').inherits; var debug = require('debug')('proxy-agent'); +var getProxyForUrl = require('proxy-from-env').getProxyForUrl; +var http = require('http'); +var https = require('https'); var PacProxyAgent = require('pac-proxy-agent'); var HttpProxyAgent = require('http-proxy-agent'); var HttpsProxyAgent = require('https-proxy-agent'); @@ -53,7 +56,17 @@ PacProxyAgent.protocols.forEach(function (protocol) { exports.proxies['pac+' + protocol] = PacProxyAgent; }); -function httpOrHttpsProxy (opts, secureEndpoint) { +function httpOrHttps(opts, secureEndpoint) { + if (secureEndpoint) { + // HTTPS + return https.globalAgent; + } else { + // HTTP + return http.globalAgent; + } +} + +function httpOrHttpsProxy(opts, secureEndpoint) { if (secureEndpoint) { // HTTPS return new HttpsProxyAgent(opts); @@ -63,25 +76,16 @@ function httpOrHttpsProxy (opts, secureEndpoint) { } } -/** - * Attempts to get an `http.Agent` instance based off of the given proxy URI - * information, and the `secure` flag. - * - * An LRU cache is used, to prevent unnecessary creation of proxy - * `http.Agent` instances. - * - * @param {String} uri proxy url - * @param {Boolean} secure true if this is for an HTTPS request, false for HTTP - * @return {http.Agent} - * @api public - */ +function mapOptsToProxy(opts) { + // NO_PROXY case + if (!opts) { + return { + uri: 'no proxy', + fn: httpOrHttps + }; + } -function ProxyAgent (opts) { - if (!(this instanceof ProxyAgent)) return new ProxyAgent(opts); if ('string' == typeof opts) opts = url.parse(opts); - if (!opts) throw new TypeError('an HTTP(S) proxy server `host` and `protocol` must be specified!'); - debug('creating new ProxyAgent instance: %o', opts); - Agent.call(this, connect); var proxies; if (opts.proxies) { @@ -108,18 +112,46 @@ function ProxyAgent (opts) { throw new TypeError('unsupported proxy protocol: "' + protocol + '"'); } - this.proxy = opts; // format the proxy info back into a URI, since an opts object - // could have been passed in originally. This generated URI is - // used as part of the "key" for the LRU cache - this.proxyUri = url.format({ - protocol: protocol + ':', - slashes: true, - auth:opts.auth, - hostname: opts.hostname || opts.host, - port: opts.port - }); - this.proxyFn = proxyFn; + // could have been passed in originally. This generated URI is used + // as part of the "key" for the LRU cache + return { + opts: opts, + uri: url.format({ + protocol: protocol + ':', + slashes: true, + auth: opts.auth, + hostname: opts.hostname || opts.host, + port: opts.port + }), + fn: proxyFn, + } +} + +/** + * Attempts to get an `http.Agent` instance based off of the given proxy URI + * information, and the `secure` flag. + * + * An LRU cache is used, to prevent unnecessary creation of proxy + * `http.Agent` instances. + * + * @param {String} uri proxy url + * @param {Boolean} secure true if this is for an HTTPS request, false for HTTP + * @return {http.Agent} + * @api public + */ + +function ProxyAgent (opts) { + if (!(this instanceof ProxyAgent)) return new ProxyAgent(opts); + debug('creating new ProxyAgent instance: %o', opts); + Agent.call(this, connect); + + if (opts) { + var proxy = mapOptsToProxy(opts); + this.proxy = proxy.opts; + this.proxyUri = proxy.uri; + this.proxyFn = proxy.fn; + } } inherits(ProxyAgent, Agent); @@ -127,16 +159,29 @@ inherits(ProxyAgent, Agent); * */ -function connect (req, opts) { +function connect (req, opts, fn) { + var proxyOpts = this.proxy; + var proxyUri = this.proxyUri; + var proxyFn = this.proxyFn; + + // if we did not instantiate with a proxy, set one per request + if (!proxyOpts) { + var urlOpts = getProxyForUrl(opts); + var proxy = mapOptsToProxy(urlOpts, opts); + proxyOpts = proxy.opts; + proxyUri = proxy.uri; + proxyFn = proxy.fn; + } + // create the "key" for the LRU cache - var key = this.proxyUri; + var key = proxyUri; if (opts.secureEndpoint) key += ' secure'; // attempt to get a cached `http.Agent` instance first var agent = exports.cache.get(key); if (!agent) { // get an `http.Agent` instance from protocol-specific agent function - agent = this.proxyFn(this.proxy, opts.secureEndpoint); + agent = proxyFn(proxyOpts, opts.secureEndpoint); if (agent) { exports.cache.set(key, agent); } @@ -144,5 +189,10 @@ function connect (req, opts) { debug('cache hit with key: %o', key); } - return agent; + if (!proxyOpts) { + agent.addRequest(req, opts); + } else { + // XXX: agent.callback() is an agent-base-ism + agent.callback(req, opts, fn); + } } diff --git a/package.json b/package.json index 20766e8..e7f5cdc 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "https-proxy-agent": "^1.0.0", "lru-cache": "^2.6.5", "pac-proxy-agent": "^2.0.0", + "proxy-from-env": "^1.0.0", "socks-proxy-agent": "^3.0.0" }, "devDependencies": { diff --git a/test/test.js b/test/test.js index facccb9..1bd9838 100644 --- a/test/test.js +++ b/test/test.js @@ -110,11 +110,6 @@ describe('ProxyAgent', function () { }); describe('constructor', function () { - it('should throw a TypeError if no "proxy" argument is given', function () { - assert.throws(function () { - new ProxyAgent(); - }, TypeError); - }); it('should throw a TypeError if no "protocol" is given', function () { assert.throws(function () { ProxyAgent({ host: 'foo.com', port: 3128 }); @@ -163,6 +158,56 @@ describe('ProxyAgent', function () { }); }); + describe('over "http" proxy from env', function () { + it('should work', function (done) { + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + }); + + process.env.HTTP_PROXY = 'http://127.0.0.1:' + proxyPort; + var agent = new ProxyAgent(); + + var opts = url.parse('http://127.0.0.1:' + httpPort + '/test'); + opts.agent = agent; + + var req = http.get(opts, function (res) { + toBuffer(res, function (err, buf) { + if (err) return done(err); + var data = JSON.parse(buf.toString('utf8')); + assert.equal('127.0.0.1:' + httpPort, data.host); + assert('via' in data); + done(); + }); + }); + req.once('error', done); + }); + }); + + describe('with no proxy from env', function () { + it('should work', function (done) { + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + }); + + process.env.NO_PROXY = '*'; + var agent = new ProxyAgent(); + + var opts = url.parse('http://127.0.0.1:' + httpPort + '/test'); + opts.agent = agent; + + var req = http.get(opts, function (res) { + toBuffer(res, function (err, buf) { + if (err) return done(err); + var data = JSON.parse(buf.toString('utf8')); + assert.equal('127.0.0.1:' + httpPort, data.host); + assert(!('via' in data)); + done(); + }); + }); + req.once('error', done); + }); + }); + describe('over "https" proxy', function () { it('should work', function (done) { httpServer.once('request', function (req, res) { @@ -213,10 +258,8 @@ describe('ProxyAgent', function () { req.once('error', done); }); }); - }); - describe('"https" module', function () { describe('over "http" proxy', function () { it('should work', function (done) { @@ -303,5 +346,4 @@ describe('ProxyAgent', function () { }); }); }); - });