232 lines
6.6 KiB
JavaScript
232 lines
6.6 KiB
JavaScript
const url = require('url')
|
|
const tunnelAgent = require('tunnel-agent')
|
|
const log = require('../../../utils/util.log')
|
|
const matchUtil = require('../../../utils/util.match')
|
|
const Agent = require('./ProxyHttpAgent')
|
|
const HttpsAgent = require('./ProxyHttpsAgent')
|
|
|
|
const util = exports
|
|
|
|
const httpsAgentCache = {}
|
|
const httpAgentCache = {}
|
|
|
|
let socketId = 0
|
|
|
|
let httpsOverHttpAgent, httpOverHttpsAgent, httpsOverHttpsAgent
|
|
|
|
function getTimeoutConfig (hostname, serverSetting) {
|
|
const timeoutMapping = serverSetting.timeoutMapping
|
|
|
|
const timeoutConfig = matchUtil.matchHostname(timeoutMapping, hostname, 'get timeoutConfig') || {}
|
|
|
|
return {
|
|
timeout: timeoutConfig.timeout || serverSetting.defaultTimeout || 20000,
|
|
keepAliveTimeout: timeoutConfig.keepAliveTimeout || serverSetting.defaultKeepAliveTimeout || 30000,
|
|
}
|
|
}
|
|
|
|
function createHttpsAgent (timeoutConfig, verifySsl) {
|
|
const key = `${timeoutConfig.timeout}-${timeoutConfig.keepAliveTimeout}`
|
|
if (!httpsAgentCache[key]) {
|
|
verifySsl = !!verifySsl
|
|
|
|
// 证书回调函数
|
|
const checkServerIdentity = (host, cert) => {
|
|
log.info(`checkServerIdentity: ${host}, CN: ${cert.subject.CN}, C: ${cert.subject.C || cert.issuer.C}, ST: ${cert.subject.ST || cert.issuer.ST}, bits: ${cert.bits}`)
|
|
}
|
|
|
|
const agent = new HttpsAgent({
|
|
keepAlive: true,
|
|
timeout: timeoutConfig.timeout,
|
|
keepAliveTimeout: timeoutConfig.keepAliveTimeout,
|
|
checkServerIdentity,
|
|
rejectUnauthorized: verifySsl,
|
|
})
|
|
|
|
agent.unVerifySslAgent = new HttpsAgent({
|
|
keepAlive: true,
|
|
timeout: timeoutConfig.timeout,
|
|
keepAliveTimeout: timeoutConfig.keepAliveTimeout,
|
|
checkServerIdentity,
|
|
rejectUnauthorized: false,
|
|
})
|
|
|
|
httpsAgentCache[key] = agent
|
|
log.info('创建 HttpsAgent 成功, timeoutConfig:', timeoutConfig, ', verifySsl:', verifySsl)
|
|
}
|
|
return httpsAgentCache[key]
|
|
}
|
|
|
|
function createHttpAgent (timeoutConfig) {
|
|
const key = `${timeoutConfig.timeout}-${timeoutConfig.keepAliveTimeout}`
|
|
if (!httpAgentCache[key]) {
|
|
httpAgentCache[key] = new Agent({
|
|
keepAlive: true,
|
|
timeout: timeoutConfig.timeout,
|
|
keepAliveTimeout: timeoutConfig.keepAliveTimeout,
|
|
})
|
|
log.info('创建 HttpAgent 成功, timeoutConfig:', timeoutConfig)
|
|
}
|
|
return httpAgentCache[key]
|
|
}
|
|
|
|
function createAgent (protocol, timeoutConfig, verifySsl) {
|
|
return protocol === 'https:'
|
|
? createHttpsAgent(timeoutConfig, verifySsl)
|
|
: createHttpAgent(timeoutConfig)
|
|
}
|
|
|
|
util.parseHostnameAndPort = (host, defaultPort) => {
|
|
let arr = host.match(/^(\[[^\]]+\])(?::(\d+))?$/) // 尝试解析IPv6
|
|
if (arr) {
|
|
arr = arr.slice(1)
|
|
if (arr[1]) {
|
|
arr[1] = Number.parseInt(arr[1], 10)
|
|
}
|
|
} else {
|
|
arr = host.split(':')
|
|
if (arr.length > 1) {
|
|
arr[1] = Number.parseInt(arr[1], 10)
|
|
}
|
|
}
|
|
|
|
if (defaultPort > 0 && (arr.length === 1 || arr[1] === undefined)) {
|
|
arr[1] = defaultPort
|
|
} else if (arr.length === 2 && arr[1] === undefined) {
|
|
arr.pop()
|
|
}
|
|
|
|
return arr
|
|
}
|
|
|
|
util.getOptionsFromRequest = (req, ssl, externalProxy = null, serverSetting, compatibleConfig = null) => {
|
|
// eslint-disable-next-line node/no-deprecated-api
|
|
const urlObject = url.parse(req.url)
|
|
const defaultPort = ssl ? 443 : 80
|
|
const protocol = ssl ? 'https:' : 'http:'
|
|
const headers = Object.assign({}, req.headers)
|
|
let externalProxyUrl = null
|
|
|
|
if (externalProxy) {
|
|
if (typeof externalProxy === 'string') {
|
|
externalProxyUrl = externalProxy
|
|
} else if (typeof externalProxy === 'function') {
|
|
try {
|
|
externalProxyUrl = externalProxy(req, ssl)
|
|
} catch (e) {
|
|
log.error('externalProxy error:', e)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 解析host和port
|
|
const arr = util.parseHostnameAndPort(req.headers.host)
|
|
const hostname = arr[0]
|
|
const port = arr[1] || defaultPort
|
|
|
|
delete headers['proxy-connection']
|
|
let agent
|
|
if (!externalProxyUrl) {
|
|
// keepAlive
|
|
if (headers.connection !== 'close') {
|
|
const timeoutConfig = getTimeoutConfig(hostname, serverSetting)
|
|
// log.info(`get timeoutConfig '${hostname}':`, timeoutConfig)
|
|
agent = createAgent(protocol, timeoutConfig, serverSetting.verifySsl)
|
|
headers.connection = 'keep-alive'
|
|
} else {
|
|
agent = false
|
|
}
|
|
} else {
|
|
agent = util.getTunnelAgent(protocol === 'https:', externalProxyUrl)
|
|
}
|
|
|
|
// 初始化options
|
|
const options = {
|
|
protocol,
|
|
method: req.method,
|
|
url: req.url,
|
|
hostname,
|
|
port,
|
|
path: urlObject.path,
|
|
headers: req.headers,
|
|
agent,
|
|
compatibleConfig,
|
|
}
|
|
|
|
// eslint-disable-next-line node/no-deprecated-api
|
|
if (protocol === 'http:' && externalProxyUrl && (url.parse(externalProxyUrl)).protocol === 'http:') {
|
|
// eslint-disable-next-line node/no-deprecated-api
|
|
const externalURL = url.parse(externalProxyUrl)
|
|
options.hostname = externalURL.hostname
|
|
options.port = externalURL.port
|
|
// support non-transparent proxy
|
|
options.path = `http://${urlObject.host}${urlObject.path}`
|
|
}
|
|
|
|
// mark a socketId for Agent to bind socket for NTLM
|
|
if (req.socket.customSocketId) {
|
|
options.customSocketId = req.socket.customSocketId
|
|
} else if (headers.authorization) {
|
|
options.customSocketId = req.socket.customSocketId = socketId++
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
util.getTunnelAgent = (requestIsSSL, externalProxyUrl) => {
|
|
// eslint-disable-next-line node/no-deprecated-api
|
|
const urlObject = url.parse(externalProxyUrl)
|
|
const protocol = urlObject.protocol || 'http:'
|
|
let port = urlObject.port
|
|
if (!port) {
|
|
port = protocol === 'http:' ? 80 : 443
|
|
}
|
|
const hostname = urlObject.hostname || 'localhost'
|
|
|
|
if (requestIsSSL) {
|
|
if (protocol === 'http:') {
|
|
if (!httpsOverHttpAgent) {
|
|
httpsOverHttpAgent = tunnelAgent.httpsOverHttp({
|
|
proxy: {
|
|
host: hostname,
|
|
port,
|
|
},
|
|
})
|
|
}
|
|
return httpsOverHttpAgent
|
|
} else {
|
|
if (!httpsOverHttpsAgent) {
|
|
httpsOverHttpsAgent = tunnelAgent.httpsOverHttps({
|
|
proxy: {
|
|
host: hostname,
|
|
port,
|
|
},
|
|
})
|
|
}
|
|
return httpsOverHttpsAgent
|
|
}
|
|
} else {
|
|
if (protocol === 'http:') {
|
|
// if (!httpOverHttpAgent) {
|
|
// httpOverHttpAgent = tunnelAgent.httpOverHttp({
|
|
// proxy: {
|
|
// host: hostname,
|
|
// port: port
|
|
// }
|
|
// })
|
|
// }
|
|
return false
|
|
} else {
|
|
if (!httpOverHttpsAgent) {
|
|
httpOverHttpsAgent = tunnelAgent.httpOverHttps({
|
|
proxy: {
|
|
host: hostname,
|
|
port,
|
|
},
|
|
})
|
|
}
|
|
return httpOverHttpsAgent
|
|
}
|
|
}
|
|
}
|