You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
5.7 KiB
214 lines
5.7 KiB
'use strict' |
|
const LRU = require('lru-cache') |
|
const url = require('url') |
|
const isLambda = require('is-lambda') |
|
const dns = require('./dns.js') |
|
|
|
const AGENT_CACHE = new LRU({ max: 50 }) |
|
const HttpAgent = require('agentkeepalive') |
|
const HttpsAgent = HttpAgent.HttpsAgent |
|
|
|
module.exports = getAgent |
|
|
|
const getAgentTimeout = timeout => |
|
typeof timeout !== 'number' || !timeout ? 0 : timeout + 1 |
|
|
|
const getMaxSockets = maxSockets => maxSockets || 15 |
|
|
|
function getAgent (uri, opts) { |
|
const parsedUri = new url.URL(typeof uri === 'string' ? uri : uri.url) |
|
const isHttps = parsedUri.protocol === 'https:' |
|
const pxuri = getProxyUri(parsedUri.href, opts) |
|
|
|
// If opts.timeout is zero, set the agentTimeout to zero as well. A timeout |
|
// of zero disables the timeout behavior (OS limits still apply). Else, if |
|
// opts.timeout is a non-zero value, set it to timeout + 1, to ensure that |
|
// the node-fetch-npm timeout will always fire first, giving us more |
|
// consistent errors. |
|
const agentTimeout = getAgentTimeout(opts.timeout) |
|
const agentMaxSockets = getMaxSockets(opts.maxSockets) |
|
|
|
const key = [ |
|
`https:${isHttps}`, |
|
pxuri |
|
? `proxy:${pxuri.protocol}//${pxuri.host}:${pxuri.port}` |
|
: '>no-proxy<', |
|
`local-address:${opts.localAddress || '>no-local-address<'}`, |
|
`strict-ssl:${isHttps ? opts.rejectUnauthorized : '>no-strict-ssl<'}`, |
|
`ca:${(isHttps && opts.ca) || '>no-ca<'}`, |
|
`cert:${(isHttps && opts.cert) || '>no-cert<'}`, |
|
`key:${(isHttps && opts.key) || '>no-key<'}`, |
|
`timeout:${agentTimeout}`, |
|
`maxSockets:${agentMaxSockets}`, |
|
].join(':') |
|
|
|
if (opts.agent != null) { // `agent: false` has special behavior! |
|
return opts.agent |
|
} |
|
|
|
// keep alive in AWS lambda makes no sense |
|
const lambdaAgent = !isLambda ? null |
|
: isHttps ? require('https').globalAgent |
|
: require('http').globalAgent |
|
|
|
if (isLambda && !pxuri) { |
|
return lambdaAgent |
|
} |
|
|
|
if (AGENT_CACHE.peek(key)) { |
|
return AGENT_CACHE.get(key) |
|
} |
|
|
|
if (pxuri) { |
|
const pxopts = isLambda ? { |
|
...opts, |
|
agent: lambdaAgent, |
|
} : opts |
|
const proxy = getProxy(pxuri, pxopts, isHttps) |
|
AGENT_CACHE.set(key, proxy) |
|
return proxy |
|
} |
|
|
|
const agent = isHttps ? new HttpsAgent({ |
|
maxSockets: agentMaxSockets, |
|
ca: opts.ca, |
|
cert: opts.cert, |
|
key: opts.key, |
|
localAddress: opts.localAddress, |
|
rejectUnauthorized: opts.rejectUnauthorized, |
|
timeout: agentTimeout, |
|
freeSocketTimeout: 15000, |
|
lookup: dns.getLookup(opts.dns), |
|
}) : new HttpAgent({ |
|
maxSockets: agentMaxSockets, |
|
localAddress: opts.localAddress, |
|
timeout: agentTimeout, |
|
freeSocketTimeout: 15000, |
|
lookup: dns.getLookup(opts.dns), |
|
}) |
|
AGENT_CACHE.set(key, agent) |
|
return agent |
|
} |
|
|
|
function checkNoProxy (uri, opts) { |
|
const host = new url.URL(uri).hostname.split('.').reverse() |
|
let noproxy = (opts.noProxy || getProcessEnv('no_proxy')) |
|
if (typeof noproxy === 'string') { |
|
noproxy = noproxy.split(',').map(n => n.trim()) |
|
} |
|
|
|
return noproxy && noproxy.some(no => { |
|
const noParts = no.split('.').filter(x => x).reverse() |
|
if (!noParts.length) { |
|
return false |
|
} |
|
for (let i = 0; i < noParts.length; i++) { |
|
if (host[i] !== noParts[i]) { |
|
return false |
|
} |
|
} |
|
return true |
|
}) |
|
} |
|
|
|
module.exports.getProcessEnv = getProcessEnv |
|
|
|
function getProcessEnv (env) { |
|
if (!env) { |
|
return |
|
} |
|
|
|
let value |
|
|
|
if (Array.isArray(env)) { |
|
for (const e of env) { |
|
value = process.env[e] || |
|
process.env[e.toUpperCase()] || |
|
process.env[e.toLowerCase()] |
|
if (typeof value !== 'undefined') { |
|
break |
|
} |
|
} |
|
} |
|
|
|
if (typeof env === 'string') { |
|
value = process.env[env] || |
|
process.env[env.toUpperCase()] || |
|
process.env[env.toLowerCase()] |
|
} |
|
|
|
return value |
|
} |
|
|
|
module.exports.getProxyUri = getProxyUri |
|
function getProxyUri (uri, opts) { |
|
const protocol = new url.URL(uri).protocol |
|
|
|
const proxy = opts.proxy || |
|
( |
|
protocol === 'https:' && |
|
getProcessEnv('https_proxy') |
|
) || |
|
( |
|
protocol === 'http:' && |
|
getProcessEnv(['https_proxy', 'http_proxy', 'proxy']) |
|
) |
|
if (!proxy) { |
|
return null |
|
} |
|
|
|
const parsedProxy = (typeof proxy === 'string') ? new url.URL(proxy) : proxy |
|
|
|
return !checkNoProxy(uri, opts) && parsedProxy |
|
} |
|
|
|
const getAuth = u => |
|
u.username && u.password ? decodeURIComponent(`${u.username}:${u.password}`) |
|
: u.username ? decodeURIComponent(u.username) |
|
: null |
|
|
|
const getPath = u => u.pathname + u.search + u.hash |
|
|
|
const HttpProxyAgent = require('http-proxy-agent') |
|
const HttpsProxyAgent = require('https-proxy-agent') |
|
const { SocksProxyAgent } = require('socks-proxy-agent') |
|
module.exports.getProxy = getProxy |
|
function getProxy (proxyUrl, opts, isHttps) { |
|
// our current proxy agents do not support an overridden dns lookup method, so will not |
|
// benefit from the dns cache |
|
const popts = { |
|
host: proxyUrl.hostname, |
|
port: proxyUrl.port, |
|
protocol: proxyUrl.protocol, |
|
path: getPath(proxyUrl), |
|
auth: getAuth(proxyUrl), |
|
ca: opts.ca, |
|
cert: opts.cert, |
|
key: opts.key, |
|
timeout: getAgentTimeout(opts.timeout), |
|
localAddress: opts.localAddress, |
|
maxSockets: getMaxSockets(opts.maxSockets), |
|
rejectUnauthorized: opts.rejectUnauthorized, |
|
} |
|
|
|
if (proxyUrl.protocol === 'http:' || proxyUrl.protocol === 'https:') { |
|
if (!isHttps) { |
|
return new HttpProxyAgent(popts) |
|
} else { |
|
return new HttpsProxyAgent(popts) |
|
} |
|
} else if (proxyUrl.protocol.startsWith('socks')) { |
|
// socks-proxy-agent uses hostname not host |
|
popts.hostname = popts.host |
|
delete popts.host |
|
return new SocksProxyAgent(popts) |
|
} else { |
|
throw Object.assign( |
|
new Error(`unsupported proxy protocol: '${proxyUrl.protocol}'`), |
|
{ |
|
code: 'EUNSUPPORTEDPROXY', |
|
url: proxyUrl.href, |
|
} |
|
) |
|
} |
|
}
|
|
|