[6a3a178] | 1 | 'use strict'
|
---|
| 2 | const LRU = require('lru-cache')
|
---|
| 3 | const url = require('url')
|
---|
| 4 | const isLambda = require('is-lambda')
|
---|
| 5 |
|
---|
| 6 | const AGENT_CACHE = new LRU({ max: 50 })
|
---|
| 7 | const HttpAgent = require('agentkeepalive')
|
---|
| 8 | const HttpsAgent = HttpAgent.HttpsAgent
|
---|
| 9 |
|
---|
| 10 | module.exports = getAgent
|
---|
| 11 |
|
---|
| 12 | const getAgentTimeout = timeout =>
|
---|
| 13 | typeof timeout !== 'number' || !timeout ? 0 : timeout + 1
|
---|
| 14 |
|
---|
| 15 | const getMaxSockets = maxSockets => maxSockets || 15
|
---|
| 16 |
|
---|
| 17 | function getAgent (uri, opts) {
|
---|
| 18 | const parsedUri = new url.URL(typeof uri === 'string' ? uri : uri.url)
|
---|
| 19 | const isHttps = parsedUri.protocol === 'https:'
|
---|
| 20 | const pxuri = getProxyUri(parsedUri.href, opts)
|
---|
| 21 |
|
---|
| 22 | // If opts.timeout is zero, set the agentTimeout to zero as well. A timeout
|
---|
| 23 | // of zero disables the timeout behavior (OS limits still apply). Else, if
|
---|
| 24 | // opts.timeout is a non-zero value, set it to timeout + 1, to ensure that
|
---|
| 25 | // the node-fetch-npm timeout will always fire first, giving us more
|
---|
| 26 | // consistent errors.
|
---|
| 27 | const agentTimeout = getAgentTimeout(opts.timeout)
|
---|
| 28 | const agentMaxSockets = getMaxSockets(opts.maxSockets)
|
---|
| 29 |
|
---|
| 30 | const key = [
|
---|
| 31 | `https:${isHttps}`,
|
---|
| 32 | pxuri
|
---|
| 33 | ? `proxy:${pxuri.protocol}//${pxuri.host}:${pxuri.port}`
|
---|
| 34 | : '>no-proxy<',
|
---|
| 35 | `local-address:${opts.localAddress || '>no-local-address<'}`,
|
---|
| 36 | `strict-ssl:${isHttps ? opts.rejectUnauthorized : '>no-strict-ssl<'}`,
|
---|
| 37 | `ca:${(isHttps && opts.ca) || '>no-ca<'}`,
|
---|
| 38 | `cert:${(isHttps && opts.cert) || '>no-cert<'}`,
|
---|
| 39 | `key:${(isHttps && opts.key) || '>no-key<'}`,
|
---|
| 40 | `timeout:${agentTimeout}`,
|
---|
| 41 | `maxSockets:${agentMaxSockets}`,
|
---|
| 42 | ].join(':')
|
---|
| 43 |
|
---|
| 44 | if (opts.agent != null) { // `agent: false` has special behavior!
|
---|
| 45 | return opts.agent
|
---|
| 46 | }
|
---|
| 47 |
|
---|
| 48 | // keep alive in AWS lambda makes no sense
|
---|
| 49 | const lambdaAgent = !isLambda ? null
|
---|
| 50 | : isHttps ? require('https').globalAgent
|
---|
| 51 | : require('http').globalAgent
|
---|
| 52 |
|
---|
| 53 | if (isLambda && !pxuri)
|
---|
| 54 | return lambdaAgent
|
---|
| 55 |
|
---|
| 56 | if (AGENT_CACHE.peek(key))
|
---|
| 57 | return AGENT_CACHE.get(key)
|
---|
| 58 |
|
---|
| 59 | if (pxuri) {
|
---|
| 60 | const pxopts = isLambda ? {
|
---|
| 61 | ...opts,
|
---|
| 62 | agent: lambdaAgent,
|
---|
| 63 | } : opts
|
---|
| 64 | const proxy = getProxy(pxuri, pxopts, isHttps)
|
---|
| 65 | AGENT_CACHE.set(key, proxy)
|
---|
| 66 | return proxy
|
---|
| 67 | }
|
---|
| 68 |
|
---|
| 69 | const agent = isHttps ? new HttpsAgent({
|
---|
| 70 | maxSockets: agentMaxSockets,
|
---|
| 71 | ca: opts.ca,
|
---|
| 72 | cert: opts.cert,
|
---|
| 73 | key: opts.key,
|
---|
| 74 | localAddress: opts.localAddress,
|
---|
| 75 | rejectUnauthorized: opts.rejectUnauthorized,
|
---|
| 76 | timeout: agentTimeout,
|
---|
| 77 | }) : new HttpAgent({
|
---|
| 78 | maxSockets: agentMaxSockets,
|
---|
| 79 | localAddress: opts.localAddress,
|
---|
| 80 | timeout: agentTimeout,
|
---|
| 81 | })
|
---|
| 82 | AGENT_CACHE.set(key, agent)
|
---|
| 83 | return agent
|
---|
| 84 | }
|
---|
| 85 |
|
---|
| 86 | function checkNoProxy (uri, opts) {
|
---|
| 87 | const host = new url.URL(uri).hostname.split('.').reverse()
|
---|
| 88 | let noproxy = (opts.noProxy || getProcessEnv('no_proxy'))
|
---|
| 89 | if (typeof noproxy === 'string')
|
---|
| 90 | noproxy = noproxy.split(/\s*,\s*/g)
|
---|
| 91 |
|
---|
| 92 | return noproxy && noproxy.some(no => {
|
---|
| 93 | const noParts = no.split('.').filter(x => x).reverse()
|
---|
| 94 | if (!noParts.length)
|
---|
| 95 | return false
|
---|
| 96 | for (let i = 0; i < noParts.length; i++) {
|
---|
| 97 | if (host[i] !== noParts[i])
|
---|
| 98 | return false
|
---|
| 99 | }
|
---|
| 100 | return true
|
---|
| 101 | })
|
---|
| 102 | }
|
---|
| 103 |
|
---|
| 104 | module.exports.getProcessEnv = getProcessEnv
|
---|
| 105 |
|
---|
| 106 | function getProcessEnv (env) {
|
---|
| 107 | if (!env)
|
---|
| 108 | return
|
---|
| 109 |
|
---|
| 110 | let value
|
---|
| 111 |
|
---|
| 112 | if (Array.isArray(env)) {
|
---|
| 113 | for (const e of env) {
|
---|
| 114 | value = process.env[e] ||
|
---|
| 115 | process.env[e.toUpperCase()] ||
|
---|
| 116 | process.env[e.toLowerCase()]
|
---|
| 117 | if (typeof value !== 'undefined')
|
---|
| 118 | break
|
---|
| 119 | }
|
---|
| 120 | }
|
---|
| 121 |
|
---|
| 122 | if (typeof env === 'string') {
|
---|
| 123 | value = process.env[env] ||
|
---|
| 124 | process.env[env.toUpperCase()] ||
|
---|
| 125 | process.env[env.toLowerCase()]
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | return value
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 | module.exports.getProxyUri = getProxyUri
|
---|
| 132 | function getProxyUri (uri, opts) {
|
---|
| 133 | const protocol = new url.URL(uri).protocol
|
---|
| 134 |
|
---|
| 135 | const proxy = opts.proxy ||
|
---|
| 136 | (
|
---|
| 137 | protocol === 'https:' &&
|
---|
| 138 | getProcessEnv('https_proxy')
|
---|
| 139 | ) ||
|
---|
| 140 | (
|
---|
| 141 | protocol === 'http:' &&
|
---|
| 142 | getProcessEnv(['https_proxy', 'http_proxy', 'proxy'])
|
---|
| 143 | )
|
---|
| 144 | if (!proxy)
|
---|
| 145 | return null
|
---|
| 146 |
|
---|
| 147 | const parsedProxy = (typeof proxy === 'string') ? new url.URL(proxy) : proxy
|
---|
| 148 |
|
---|
| 149 | return !checkNoProxy(uri, opts) && parsedProxy
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | const getAuth = u =>
|
---|
| 153 | u.username && u.password ? decodeURIComponent(`${u.username}:${u.password}`)
|
---|
| 154 | : u.username ? decodeURIComponent(u.username)
|
---|
| 155 | : null
|
---|
| 156 |
|
---|
| 157 | const getPath = u => u.pathname + u.search + u.hash
|
---|
| 158 |
|
---|
| 159 | const HttpProxyAgent = require('http-proxy-agent')
|
---|
| 160 | const HttpsProxyAgent = require('https-proxy-agent')
|
---|
| 161 | const SocksProxyAgent = require('socks-proxy-agent')
|
---|
| 162 | module.exports.getProxy = getProxy
|
---|
| 163 | function getProxy (proxyUrl, opts, isHttps) {
|
---|
| 164 | const popts = {
|
---|
| 165 | host: proxyUrl.hostname,
|
---|
| 166 | port: proxyUrl.port,
|
---|
| 167 | protocol: proxyUrl.protocol,
|
---|
| 168 | path: getPath(proxyUrl),
|
---|
| 169 | auth: getAuth(proxyUrl),
|
---|
| 170 | ca: opts.ca,
|
---|
| 171 | cert: opts.cert,
|
---|
| 172 | key: opts.key,
|
---|
| 173 | timeout: getAgentTimeout(opts.timeout),
|
---|
| 174 | localAddress: opts.localAddress,
|
---|
| 175 | maxSockets: getMaxSockets(opts.maxSockets),
|
---|
| 176 | rejectUnauthorized: opts.rejectUnauthorized,
|
---|
| 177 | }
|
---|
| 178 |
|
---|
| 179 | if (proxyUrl.protocol === 'http:' || proxyUrl.protocol === 'https:') {
|
---|
| 180 | if (!isHttps)
|
---|
| 181 | return new HttpProxyAgent(popts)
|
---|
| 182 | else
|
---|
| 183 | return new HttpsProxyAgent(popts)
|
---|
| 184 | } else if (proxyUrl.protocol.startsWith('socks'))
|
---|
| 185 | return new SocksProxyAgent(popts)
|
---|
| 186 | else {
|
---|
| 187 | throw Object.assign(
|
---|
| 188 | new Error(`unsupported proxy protocol: '${proxyUrl.protocol}'`),
|
---|
| 189 | {
|
---|
| 190 | url: proxyUrl.href,
|
---|
| 191 | }
|
---|
| 192 | )
|
---|
| 193 | }
|
---|
| 194 | }
|
---|