bugfix: 修复因 `HttpsAgent` 为单例,导致并发请求时因启用了SSL校验导致 proxy 或 sni 请求失败的问题

pull/375/head
王良 2024-09-29 13:12:49 +08:00
parent 6064fe4ab1
commit bf84613f51
4 changed files with 34 additions and 20 deletions

View File

@ -116,12 +116,16 @@ module.exports = {
} }
if (interceptOpt.sni != null) { if (interceptOpt.sni != null) {
let unVerifySsl = rOptions.agent.options.rejectUnauthorized === false
rOptions.servername = interceptOpt.sni rOptions.servername = interceptOpt.sni
if (rOptions.agent && rOptions.agent.options) { if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
rOptions.agent.options.rejectUnauthorized = false // rOptions.agent.options.rejectUnauthorized = false // 不能直接在agent上进行修改属性值因为它采用了单例模式所有请求共用这个对象的
rOptions.agent = rOptions.agent.unVerifySslAgent
unVerifySsl = true
} }
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}, sni: ${interceptOpt.sni}`) res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}, sni: ${interceptOpt.sni}`, (unVerifySsl ? ', unVerifySsl' : ''))
log.info('proxy intercept: hostname:', originHostname, ', target', proxyTarget, ', sni replace servername:', rOptions.servername) log.info('proxy intercept: hostname:', originHostname, ', target', proxyTarget, ', sni replace servername:', rOptions.servername, (unVerifySsl ? ', unVerifySsl' : ''))
} else { } else {
res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}`) res.setHeader('DS-Interceptor', `proxy: ${proxyTarget}`)
log.info('proxy intercept: hostname:', originHostname, ', target', proxyTarget) log.info('proxy intercept: hostname:', originHostname, ', target', proxyTarget)

View File

@ -4,14 +4,17 @@ module.exports = {
requestIntercept (context, interceptOpt, req, res, ssl, next) { requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context const { rOptions, log } = context
let unVerifySsl = rOptions.agent.options.rejectUnauthorized === false
rOptions.servername = interceptOpt.sni rOptions.servername = interceptOpt.sni
if (rOptions.agent && rOptions.agent.options) { if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
rOptions.agent.options.rejectUnauthorized = false // rOptions.agent.options.rejectUnauthorized = false // 不能直接在agent上进行修改属性值因为它采用了单例模式所有请求共用这个对象的
rOptions.agent = rOptions.agent.unVerifySslAgent
unVerifySsl = true
} }
res.setHeader('DS-Interceptor', `sni: ${interceptOpt.sni}`, (unVerifySsl ? ', unVerifySsl' : ''))
res.setHeader('DS-Interceptor', 'sni: ' + interceptOpt.sni) log.info('sni intercept: sni replace servername:', rOptions.hostname, '➜', rOptions.servername, (unVerifySsl ? ', unVerifySsl' : ''))
log.info('sni intercept: sni replace servername:', rOptions.hostname, '➜', rOptions.servername)
return true return true
}, },
is (interceptOpt) { is (interceptOpt) {

View File

@ -24,15 +24,27 @@ function getTimeoutConfig (hostname, serverSetting) {
} }
} }
function createHttpsAgent (timeoutConfig) { function createHttpsAgent (timeoutConfig, verifySsl) {
const key = timeoutConfig.timeout + '-' + timeoutConfig.keepAliveTimeout const key = timeoutConfig.timeout + '-' + timeoutConfig.keepAliveTimeout
if (!httpsAgentCache[key]) { if (!httpsAgentCache[key]) {
httpsAgentCache[key] = new HttpsAgent({ verifySsl = !!verifySsl
const agent = new HttpsAgent({
keepAlive: true,
timeout: timeoutConfig.timeout,
keepAliveTimeout: timeoutConfig.keepAliveTimeout,
rejectUnauthorized: verifySsl
})
agent.unVerifySslAgent = new HttpsAgent({
keepAlive: true, keepAlive: true,
timeout: timeoutConfig.timeout, timeout: timeoutConfig.timeout,
keepAliveTimeout: timeoutConfig.keepAliveTimeout, keepAliveTimeout: timeoutConfig.keepAliveTimeout,
rejectUnauthorized: false rejectUnauthorized: false
}) })
httpsAgentCache[key] = agent
log.info('创建 HttpsAgent 成功, timeoutConfig:', timeoutConfig, ', verifySsl:', verifySsl)
} }
return httpsAgentCache[key] return httpsAgentCache[key]
} }
@ -45,13 +57,14 @@ function createHttpAgent (timeoutConfig) {
timeout: timeoutConfig.timeout, timeout: timeoutConfig.timeout,
keepAliveTimeout: timeoutConfig.keepAliveTimeout keepAliveTimeout: timeoutConfig.keepAliveTimeout
}) })
log.info('创建 HttpsAgent 成功, timeoutConfig:', timeoutConfig)
} }
return httpAgentCache[key] return httpAgentCache[key]
} }
function createAgent (protocol, timeoutConfig) { function createAgent (protocol, timeoutConfig, verifySsl) {
return protocol === 'https:' return protocol === 'https:'
? createHttpsAgent(timeoutConfig) ? createHttpsAgent(timeoutConfig, verifySsl)
: createHttpAgent(timeoutConfig) : createHttpAgent(timeoutConfig)
} }
@ -110,7 +123,7 @@ util.getOptionsFromRequest = (req, ssl, externalProxy = null, serverSetting) =>
if (headers.connection !== 'close') { if (headers.connection !== 'close') {
const timeoutConfig = getTimeoutConfig(hostname, serverSetting) const timeoutConfig = getTimeoutConfig(hostname, serverSetting)
// log.info(`get timeoutConfig '${hostname}':`, timeoutConfig) // log.info(`get timeoutConfig '${hostname}':`, timeoutConfig)
agent = createAgent(protocol, timeoutConfig) agent = createAgent(protocol, timeoutConfig, serverSetting.verifySsl)
headers.connection = 'keep-alive' headers.connection = 'keep-alive'
} else { } else {
agent = false agent = false

View File

@ -18,12 +18,6 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
const rOptions = commonUtil.getOptionsFromRequest(req, ssl, externalProxy, setting) const rOptions = commonUtil.getOptionsFromRequest(req, ssl, externalProxy, setting)
let url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}` let url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
if (rOptions.agent) {
rOptions.agent.options.rejectUnauthorized = setting.verifySsl
} else if (rOptions.agent !== false) {
log.error('rOptions.agent 的值有问题:', rOptions)
}
if (rOptions.headers.connection === 'close') { if (rOptions.headers.connection === 'close') {
req.socket.setKeepAlive(false) req.socket.setKeepAlive(false)
} else if (rOptions.customSocketId != null) { // for NTLM } else if (rOptions.customSocketId != null) { // for NTLM