mirror of
https://github.com/tenrok/axios.git
synced 2026-05-24 14:04:14 +03:00
Resolving proxy from env on redirect (#4436)
* Fixing http adapter to recompute proxy on redirect Redirections can target different hosts or change the protocol from http to https or vice versa. When the proxy option is inferred from the environment, it should be recomputed when the protocol or host changes because the proxy host can differ or even whether to proxy or not can differ. * Fixing proxy protocol handling 1) setProxy now changes request options protocol when using a proxy with explicit protocol. 2) As a result, selection of the correct transport can be simplified. 3) Legacy agent selection needs to be moved done accordingly. (Is 'agent' option even still used?) * Using proxy-from-env library to handle proxy env vars The proxy-from-env library is a popular, lightweight library that is very easy to use and covers a few more cases, not to mention it has extensive test coverage. * Fixing proxy auth handling * Adding test proving env vars are re-resolved on redirect * Revert unnecessary change * Fixing proxy beforeRedirect regression * Fixing lint errors * Revert "Fixing lint errors" This reverts commit 2de3cabc60db2444e63a699bae9ec45531218a84. * Revert "Fixing proxy beforeRedirect regression" This reverts commit 57befc3215980e47333fedc1e9028cc22297540b.
This commit is contained in:
+43
-75
@@ -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;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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) {
|
||||
|
||||
+2
-1
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user