diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 493725031f..bdf6d37f3a 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -4,6 +4,7 @@ var utils = require('./../utils'); var settle = require('./../core/settle'); var buildFullPath = require('../core/buildFullPath'); var buildURL = require('./../helpers/buildURL'); +var getProxyForUrl = require('proxy-from-env').getProxyForUrl; var http = require('http'); var https = require('https'); var httpFollow = require('follow-redirects').http; @@ -22,25 +23,46 @@ var supportedProtocols = [ 'http:', 'https:', 'file:' ]; /** * * @param {http.ClientRequestArgs} options - * @param {AxiosProxyConfig} proxy + * @param {AxiosProxyConfig} configProxy * @param {string} location */ -function setProxy(options, proxy, location) { - options.hostname = proxy.host; - options.host = proxy.host; - options.port = proxy.port; - options.path = location; - - // Basic proxy authorization - if (proxy.auth) { - var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64'); - options.headers['Proxy-Authorization'] = 'Basic ' + base64; +function setProxy(options, configProxy, location) { + var proxy = configProxy; + if (!proxy && proxy !== false) { + var proxyUrl = getProxyForUrl(location); + if (proxyUrl) { + proxy = url.parse(proxyUrl); + // replace 'host' since the proxy object is not a URL object + proxy.host = proxy.hostname; + } + } + if (proxy) { + // Basic proxy authorization + if (proxy.auth) { + // Support proxy auth object form + if (proxy.auth.username || proxy.auth.password) { + proxy.auth = (proxy.auth.username || '') + ':' + (proxy.auth.password || ''); + } + var base64 = Buffer + .from(proxy.auth, 'utf8') + .toString('base64'); + options.headers['Proxy-Authorization'] = 'Basic ' + base64; + } + + options.headers.host = options.hostname + (options.port ? ':' + options.port : ''); + options.hostname = proxy.host; + options.host = proxy.host; + options.port = proxy.port; + options.path = location; + if (proxy.protocol) { + options.protocol = proxy.protocol; + } } - // If a proxy is used, any redirects must also pass through the proxy - options.beforeRedirect = function beforeRedirect(redirection) { - redirection.headers.host = redirection.host; - setProxy(redirection, proxy, redirection.href); + options.beforeRedirect = function beforeRedirect(redirectOptions) { + // Configure proxy for redirected request, passing the original config proxy to apply + // the exact same logic as if the redirected request was performed by axios directly. + setProxy(redirectOptions, configProxy, redirectOptions.href); }; } @@ -152,9 +174,6 @@ module.exports = function httpAdapter(config) { delete headers[headerNames.authorization]; } - var isHttpsRequest = isHttps.test(protocol); - var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent; - try { buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''); } catch (err) { @@ -169,9 +188,9 @@ module.exports = function httpAdapter(config) { path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''), method: config.method.toUpperCase(), headers: headers, - agent: agent, agents: { http: config.httpAgent, https: config.httpsAgent }, - auth: auth + auth: auth, + protocol: protocol }; if (config.socketPath) { @@ -179,67 +198,16 @@ module.exports = function httpAdapter(config) { } else { options.hostname = parsed.hostname; options.port = parsed.port; - } - - var proxy = config.proxy; - if (!proxy && proxy !== false) { - var proxyEnv = protocol.slice(0, -1) + '_proxy'; - var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()]; - if (proxyUrl) { - var parsedProxyUrl = url.parse(proxyUrl); - var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY; - var shouldProxy = true; - - if (noProxyEnv) { - var noProxy = noProxyEnv.split(',').map(function trim(s) { - return s.trim(); - }); - - shouldProxy = !noProxy.some(function proxyMatch(proxyElement) { - if (!proxyElement) { - return false; - } - if (proxyElement === '*') { - return true; - } - if (proxyElement[0] === '.' && - parsed.hostname.slice(parsed.hostname.length - proxyElement.length) === proxyElement) { - return true; - } - - return parsed.hostname === proxyElement; - }); - } - - if (shouldProxy) { - proxy = { - host: parsedProxyUrl.hostname, - port: parsedProxyUrl.port, - protocol: parsedProxyUrl.protocol - }; - - if (parsedProxyUrl.auth) { - var proxyUrlAuth = parsedProxyUrl.auth.split(':'); - proxy.auth = { - username: proxyUrlAuth[0], - password: proxyUrlAuth[1] - }; - } - } - } - } - - if (proxy) { - options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : ''); - setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path); + setProxy(options, config.proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path); } var transport; - var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true); + var isHttpsRequest = isHttps.test(options.protocol); + options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent; if (config.transport) { transport = config.transport; } else if (config.maxRedirects === 0) { - transport = isHttpsProxy ? https : http; + transport = isHttpsRequest ? https : http; } else { if (config.maxRedirects) { options.maxRedirects = config.maxRedirects; @@ -247,7 +215,7 @@ module.exports = function httpAdapter(config) { if (config.beforeRedirect) { options.beforeRedirect = config.beforeRedirect; } - transport = isHttpsProxy ? httpsFollow : httpFollow; + transport = isHttpsRequest ? httpsFollow : httpFollow; } if (config.maxBodyLength > -1) { diff --git a/package.json b/package.json index 88a6defc3d..5be6f78059 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,8 @@ "typings": "./index.d.ts", "dependencies": { "follow-redirects": "^1.15.0", - "form-data": "^4.0.0" + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" }, "bundlesize": [ { diff --git a/test/unit/adapters/http.js b/test/unit/adapters/http.js index 05adfea251..5264f63362 100644 --- a/test/unit/adapters/http.js +++ b/test/unit/adapters/http.js @@ -687,7 +687,7 @@ describe('supports http with nodejs', function () { proxy: { host: 'localhost', port: 4000, - protocol: 'https' + protocol: 'https:' }, httpsAgent: new https.Agent({ rejectUnauthorized: false @@ -798,6 +798,56 @@ describe('supports http with nodejs', function () { }); }); + it('should re-evaluate proxy on redirect when proxy set via env var', function (done) { + process.env.http_proxy = 'http://localhost:4000' + process.env.no_proxy = 'localhost:4000' + + var proxyUseCount = 0; + + server = http.createServer(function (req, res) { + res.setHeader('Location', 'http://localhost:4000/redirected'); + res.statusCode = 302; + res.end(); + }).listen(4444, function () { + proxy = http.createServer(function (request, response) { + var parsed = url.parse(request.url); + if (parsed.pathname === '/redirected') { + response.statusCode = 200; + response.end(); + return; + } + + proxyUseCount += 1; + + var opts = { + host: parsed.hostname, + port: parsed.port, + path: parsed.path, + protocol: parsed.protocol, + rejectUnauthorized: false + }; + + http.get(opts, function (res) { + var body = ''; + res.on('data', function (data) { + body += data; + }); + res.on('end', function () { + response.setHeader('Content-Type', 'text/html; charset=UTF-8'); + response.setHeader('Location', res.headers.location); + response.end(body); + }); + }); + }).listen(4000, function () { + axios.get('http://localhost:4444/').then(function(res) { + assert.equal(res.status, 200); + assert.equal(proxyUseCount, 1); + done(); + }).catch(done); + }); + }); + }); + it('should not use proxy for domains in no_proxy', function (done) { server = http.createServer(function (req, res) { res.setHeader('Content-Type', 'text/html; charset=UTF-8');