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 | }
|
---|