-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f916d33
commit 5d0d859
Showing
56 changed files
with
5,696 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use strict' | ||
|
||
const LRUCache = require('lru-cache') | ||
const dns = require('dns') | ||
|
||
const defaultOptions = exports.defaultOptions = { | ||
family: undefined, | ||
hints: dns.ADDRCONFIG, | ||
all: false, | ||
verbatim: undefined, | ||
} | ||
|
||
const lookupCache = exports.lookupCache = new LRUCache({ max: 50 }) | ||
|
||
// this is a factory so that each request can have its own opts (i.e. ttl) | ||
// while still sharing the cache across all requests | ||
exports.getLookup = (dnsOptions) => { | ||
return (hostname, options, callback) => { | ||
if (typeof options === 'function') { | ||
callback = options | ||
options = null | ||
} else if (typeof options === 'number') { | ||
options = { family: options } | ||
} | ||
|
||
options = { ...defaultOptions, ...options } | ||
|
||
const key = JSON.stringify({ | ||
hostname, | ||
family: options.family, | ||
hints: options.hints, | ||
all: options.all, | ||
verbatim: options.verbatim, | ||
}) | ||
|
||
if (lookupCache.has(key)) { | ||
const [address, family] = lookupCache.get(key) | ||
process.nextTick(callback, null, address, family) | ||
return | ||
} | ||
|
||
dnsOptions.lookup(hostname, options, (err, address, family) => { | ||
if (err) { | ||
return callback(err) | ||
} | ||
|
||
lookupCache.set(key, [address, family], { ttl: dnsOptions.ttl }) | ||
return callback(null, address, family) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
'use strict' | ||
|
||
class InvalidProxyProtocolError extends Error { | ||
constructor (url) { | ||
super(`Invalid protocol \`${url.protocol}\` connecting to proxy \`${url.host}\``) | ||
this.code = 'EINVALIDPROXY' | ||
this.proxy = url | ||
} | ||
} | ||
|
||
class InvalidProxyResponseError extends Error { | ||
constructor (url, status) { | ||
super(`Invalid status code \`${status}\` connecting to proxy \`${url.host}\``) | ||
this.code = 'EINVALIDRESPONSE' | ||
this.proxy = url | ||
this.status = status | ||
} | ||
} | ||
|
||
class ConnectionTimeoutError extends Error { | ||
constructor (host) { | ||
super(`Timeout connecting to host \`${host}\``) | ||
this.code = 'ECONNECTIONTIMEOUT' | ||
this.host = host | ||
} | ||
} | ||
|
||
class IdleTimeoutError extends Error { | ||
constructor (host) { | ||
super(`Idle timeout reached for host \`${host}\``) | ||
this.code = 'EIDLETIMEOUT' | ||
this.host = host | ||
} | ||
} | ||
|
||
class ResponseTimeoutError extends Error { | ||
constructor (proxy, request) { | ||
let msg = 'Response timeout ' | ||
if (proxy.url) { | ||
msg += `from proxy \`${proxy.url.host}\` ` | ||
} | ||
msg += `connecting to host \`${request.host}\`` | ||
super(msg) | ||
this.code = 'ERESPONSETIMEOUT' | ||
this.proxy = proxy.url | ||
this.request = request | ||
} | ||
} | ||
|
||
class TransferTimeoutError extends Error { | ||
constructor (proxy, request) { | ||
let msg = 'Transfer timeout ' | ||
if (proxy.url) { | ||
msg += `from proxy \`${proxy.url.host}\` ` | ||
} | ||
msg += `for \`${request.host}\`` | ||
super(msg) | ||
this.code = 'ETRANSFERTIMEOUT' | ||
this.proxy = proxy.url | ||
this.request = request | ||
} | ||
} | ||
|
||
module.exports = { | ||
InvalidProxyProtocolError, | ||
InvalidProxyResponseError, | ||
ConnectionTimeoutError, | ||
IdleTimeoutError, | ||
ResponseTimeoutError, | ||
TransferTimeoutError, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use strict' | ||
|
||
const http = require('http') | ||
|
||
const { getLookup } = require('./dns.js') | ||
const { normalizeOptions } = require('./util.js') | ||
const createProxy = require('./proxy/index.js') | ||
|
||
class HttpAgent extends http.Agent { | ||
constructor (_options = {}) { | ||
const options = normalizeOptions(_options) | ||
super(options) | ||
this.proxy = createProxy({ | ||
agent: this, | ||
lookup: getLookup(options.dns), | ||
proxy: options.proxy, | ||
secure: false, | ||
}) | ||
} | ||
|
||
createConnection (_options, callback) { | ||
const options = normalizeOptions(_options) | ||
return this.proxy.createConnection(options, callback) | ||
} | ||
|
||
addRequest (request, _options) { | ||
const options = normalizeOptions(_options) | ||
super.addRequest(request, _options) | ||
return this.proxy.addRequest(request, options) | ||
} | ||
} | ||
|
||
module.exports = HttpAgent |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use strict' | ||
|
||
const https = require('https') | ||
|
||
const { getLookup } = require('./dns.js') | ||
const { normalizeOptions } = require('./util.js') | ||
const createProxy = require('./proxy/index.js') | ||
|
||
class HttpsAgent extends https.Agent { | ||
constructor (_options) { | ||
const options = normalizeOptions(_options) | ||
super(options) | ||
this.proxy = createProxy({ | ||
agent: this, | ||
lookup: getLookup(options.dns), | ||
proxy: options.proxy, | ||
secure: true, | ||
}) | ||
} | ||
|
||
createConnection (_options, callback) { | ||
const options = normalizeOptions(_options) | ||
return this.proxy.createConnection(options, callback) | ||
} | ||
|
||
addRequest (request, _options) { | ||
const options = normalizeOptions(_options) | ||
super.addRequest(request, options) | ||
return this.proxy.addRequest(request, options) | ||
} | ||
} | ||
|
||
module.exports = HttpsAgent |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
'use strict' | ||
|
||
const { normalizeOptions } = require('./util.js') | ||
const HttpAgent = require('./http.js') | ||
const HttpsAgent = require('./https.js') | ||
|
||
const AgentCache = new Map() | ||
|
||
const proxyEnv = {} | ||
for (const [key, value] of Object.entries(process.env)) { | ||
const lowerKey = key.toLowerCase() | ||
if (['https_proxy', 'http_proxy', 'proxy', 'no_proxy'].includes(lowerKey)) { | ||
proxyEnv[lowerKey] = value | ||
} | ||
} | ||
|
||
const getAgent = (url, options) => { | ||
url = new URL(url) | ||
options = normalizeOptions(options) | ||
|
||
// false has meaning so this can't be a simple truthiness check | ||
if (options.agent != null) { | ||
return options.agent | ||
} | ||
|
||
const isHttps = url.protocol === 'https:' | ||
|
||
let proxy = options.proxy | ||
if (!proxy) { | ||
proxy = isHttps | ||
? proxyEnv.https_proxy | ||
: (proxyEnv.https_proxy || proxyEnv.http_proxy || proxyEnv.proxy) | ||
} | ||
|
||
if (proxy) { | ||
proxy = new URL(proxy) | ||
let noProxy = options.noProxy || proxyEnv.no_proxy | ||
if (typeof noProxy === 'string') { | ||
noProxy = noProxy.split(',').map((p) => p.trim()) | ||
} | ||
|
||
if (noProxy) { | ||
const hostSegments = url.hostname.split('.').reverse() | ||
const matches = noProxy.some((no) => { | ||
const noSegments = no.split('.').filter(Boolean).reverse() | ||
if (!noSegments.length) { | ||
return false | ||
} | ||
|
||
for (let i = 0; i < noSegments.length; ++i) { | ||
if (hostSegments[i] !== noSegments[i]) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
}) | ||
|
||
if (matches) { | ||
proxy = '' | ||
} | ||
} | ||
} | ||
|
||
const timeouts = [ | ||
options.timeouts.connection || 0, | ||
options.timeouts.idle || 0, | ||
options.timeouts.response || 0, | ||
options.timeouts.transfer || 0, | ||
].join('.') | ||
|
||
const maxSockets = options.maxSockets || 15 | ||
|
||
let proxyDescriptor = 'proxy:' | ||
if (!proxy) { | ||
proxyDescriptor += 'null' | ||
} else { | ||
proxyDescriptor += `${proxy.protocol}//` | ||
let auth = '' | ||
|
||
if (proxy.username) { | ||
auth += proxy.username | ||
} | ||
|
||
if (proxy.password) { | ||
auth += `:${proxy.password}` | ||
} | ||
|
||
if (auth) { | ||
proxyDescriptor += `${auth}@` | ||
} | ||
|
||
proxyDescriptor += proxy.host | ||
} | ||
|
||
const key = [ | ||
`https:${isHttps}`, | ||
proxyDescriptor, | ||
`local-address:${options.localAddress || 'null'}`, | ||
`strict-ssl:${isHttps ? options.rejectUnauthorized : 'false'}`, | ||
`ca:${isHttps && options.ca || 'null'}`, | ||
`cert:${isHttps && options.cert || 'null'}`, | ||
`key:${isHttps && options.key || 'null'}`, | ||
`timeouts:${timeouts}`, | ||
`maxSockets:${maxSockets}`, | ||
].join(':') | ||
|
||
if (AgentCache.has(key)) { | ||
return AgentCache.get(key) | ||
} | ||
|
||
const agentOptions = { | ||
ca: options.ca, | ||
cert: options.cert, | ||
key: options.key, | ||
rejectUnauthorized: options.rejectUnauthorized, | ||
maxSockets, | ||
timeouts: options.timeouts, | ||
localAddress: options.localAddress, | ||
proxy, | ||
} | ||
|
||
const agent = isHttps | ||
? new HttpsAgent(agentOptions) | ||
: new HttpAgent(agentOptions) | ||
|
||
AgentCache.set(key, agent) | ||
return agent | ||
} | ||
|
||
module.exports = { | ||
getAgent, | ||
HttpAgent, | ||
HttpsAgent, | ||
} |
Oops, something went wrong.