refactor: dns优选
parent
344c732ad6
commit
dfd2db5285
|
@ -24,7 +24,7 @@ module.exports = {
|
|||
'.*': { proxy: 'raw.fastgit.org' }
|
||||
},
|
||||
'github.githubassets.com': {
|
||||
'.*': { proxy: 'assets.fastgit.org' }
|
||||
'.*': { proxy: 'assets.fastgit.org', test: 'https://github.githubassets.com/favicons/favicon.svg' }
|
||||
},
|
||||
'customer-stories-feed.github.com': {
|
||||
'.*': { proxy: 'customer-stories-feed.fastgit.org' }
|
||||
|
|
|
@ -8,7 +8,7 @@ process.on('uncaughtException', function (err) {
|
|||
// console.error(err.errno)
|
||||
return
|
||||
}
|
||||
console.error('uncaughtException',err)
|
||||
console.error('uncaughtException', err)
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
|
|
|
@ -137,6 +137,7 @@ const NodePlugin = function (context) {
|
|||
]
|
||||
const ret = await shell.exec(cmds, { type: 'cmd' })
|
||||
event.fire('status', { key: 'plugin.node.enabled', value: false })
|
||||
console.info('关闭【NPM】代理成功')
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,9 +97,9 @@ const serverApi = {
|
|||
}
|
||||
})
|
||||
},
|
||||
async restart () {
|
||||
async restart ({ mitmproxyPath }) {
|
||||
await serverApi.kill()
|
||||
await serverApi.start()
|
||||
await serverApi.start({ mitmproxyPath })
|
||||
},
|
||||
getServer () {
|
||||
return server
|
||||
|
|
|
@ -35,7 +35,7 @@ function setTray (app) {
|
|||
}
|
||||
]
|
||||
// 设置系统托盘图标
|
||||
const iconPath = path.join(__dirname, '../extra/favicon.ico')
|
||||
const iconPath = path.join(__dirname, '../extra/icons/128x128.png')
|
||||
const appTray = new Tray(iconPath)
|
||||
|
||||
// 图标的上下文菜单
|
||||
|
@ -44,14 +44,25 @@ function setTray (app) {
|
|||
// 设置托盘悬浮提示
|
||||
appTray.setToolTip('DevSidecar-开发者边车辅助工具')
|
||||
|
||||
// 设置托盘菜单
|
||||
appTray.setContextMenu(contextMenu)
|
||||
|
||||
// 单击托盘小图标显示应用
|
||||
appTray.on('click', () => {
|
||||
// 显示主程序
|
||||
win.show()
|
||||
})
|
||||
|
||||
// 设置托盘菜单
|
||||
// appTray.setContextMenu(contextMenu)
|
||||
|
||||
// appTray.on('double-click', function () {
|
||||
// console.log('double click')
|
||||
// win.show()
|
||||
// })
|
||||
appTray.on('right-click', function (event, bounds) {
|
||||
setTimeout(function () {
|
||||
appTray.popUpContextMenu(contextMenu)
|
||||
}, 200)
|
||||
})
|
||||
|
||||
return appTray
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@ const localApi = {
|
|||
server: {
|
||||
start () {
|
||||
return DevSidecar.api.server.start({ mitmproxyPath })
|
||||
},
|
||||
restart () {
|
||||
return DevSidecar.api.server.restart({ mitmproxyPath })
|
||||
}
|
||||
},
|
||||
config: {
|
||||
|
|
|
@ -22,7 +22,7 @@ function register (app) {
|
|||
}
|
||||
|
||||
function handleServerStartError (err, app) {
|
||||
if (err.message.indexOf('listen EADDRINUSE') >= 0) {
|
||||
if (err.message && err.message.indexOf('listen EADDRINUSE') >= 0) {
|
||||
app.$confirm({
|
||||
title: '端口被占用,代理服务启动失败',
|
||||
content: '是否要杀掉占用进程?',
|
||||
|
|
|
@ -10,7 +10,8 @@ export default {
|
|||
config: undefined,
|
||||
status: status,
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 20 }
|
||||
wrapperCol: { span: 20 },
|
||||
applyLoading: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -27,12 +28,17 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
apply () {
|
||||
return this.saveConfig().then(() => {
|
||||
if (this.applyAfter) {
|
||||
return this.applyAfter()
|
||||
}
|
||||
})
|
||||
async apply () {
|
||||
this.applyLoading = true
|
||||
await this.applyBefore()
|
||||
await this.saveConfig()
|
||||
if (this.applyAfter) {
|
||||
await this.applyAfter()
|
||||
}
|
||||
this.applyLoading = false
|
||||
},
|
||||
async applyBefore () {
|
||||
|
||||
},
|
||||
reloadDefault (key) {
|
||||
this.$api.config.resetDefault(key).then(ret => {
|
||||
|
|
|
@ -59,8 +59,8 @@
|
|||
</div>
|
||||
<template slot="footer">
|
||||
<div class="footer-bar">
|
||||
<a-button class="md-mr-10" @click="reloadDefault('plugin.node')">恢复默认</a-button>
|
||||
<a-button type="primary" @click="apply()">应用</a-button>
|
||||
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')">恢复默认</a-button>
|
||||
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()">应用</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</ds-container>
|
||||
|
@ -91,13 +91,13 @@ export default {
|
|||
this.npmVariables = ret
|
||||
})
|
||||
},
|
||||
onSwitchRegistry (event) {
|
||||
return this.setRegistry(event.target.value).then(() => {
|
||||
this.$message.success('切换成功')
|
||||
})
|
||||
async onSwitchRegistry (event) {
|
||||
await this.setRegistry(event.target.value)
|
||||
this.$message.success('切换成功')
|
||||
},
|
||||
setRegistry (registry) {
|
||||
return this.$api.plugin.node.setRegistry(registry)
|
||||
async setRegistry (registry) {
|
||||
this.apply()
|
||||
await this.$api.plugin.node.setRegistry(registry)
|
||||
},
|
||||
setNpmVariableAll () {
|
||||
this.saveConfig().then(() => {
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
</div>
|
||||
<template slot="footer">
|
||||
<div class="footer-bar">
|
||||
<a-button class="md-mr-10" @click="reloadDefault('proxy')">恢复默认</a-button>
|
||||
<a-button type="primary" @click="apply()">应用</a-button>
|
||||
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')">恢复默认</a-button>
|
||||
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()">应用</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</ds-container>
|
||||
|
|
|
@ -69,8 +69,8 @@
|
|||
</div>
|
||||
<template slot="footer">
|
||||
<div class="footer-bar">
|
||||
<a-button class="md-mr-10" @click="reloadDefault('server')">恢复默认</a-button>
|
||||
<a-button type="primary" @click="apply()">应用</a-button>
|
||||
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')">恢复默认</a-button>
|
||||
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()">应用</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</ds-container>
|
||||
|
@ -109,8 +109,28 @@ export default {
|
|||
})
|
||||
}
|
||||
},
|
||||
applyAfter () {
|
||||
async applyBefore () {
|
||||
const dnsMapping = {}
|
||||
for (const item of this.dnsMappings) {
|
||||
if (item.key) {
|
||||
dnsMapping[item.key] = item.value
|
||||
}
|
||||
}
|
||||
this.config.server.dns.mapping = dnsMapping
|
||||
},
|
||||
async applyAfter () {
|
||||
if (this.status.server.enabled) {
|
||||
return this.$api.server.restart()
|
||||
}
|
||||
},
|
||||
deleteDnsMapping (item, index) {
|
||||
this.dnsMappings.splice(index, 1)
|
||||
},
|
||||
restoreDefDnsMapping (item, index) {
|
||||
|
||||
},
|
||||
addDnsMapping () {
|
||||
this.dnsMappings.unshift({ key: '', value: 'usa' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,72 @@
|
|||
const LRU = require('lru-cache')
|
||||
const { isIP } = require('validator')
|
||||
// const { isIP } = require('validator')
|
||||
const getLogger = require('../utils/logger')
|
||||
|
||||
const logger = getLogger('dns')
|
||||
const cacheSize = 1024
|
||||
function _isIP (v) {
|
||||
return v && isIP(v)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// function _isIP (v) {
|
||||
// return v && isIP(v)
|
||||
// }
|
||||
|
||||
class IpCache {
|
||||
constructor (hostname) {
|
||||
this.hostname = hostname
|
||||
this.count = {}
|
||||
this.lookupCount = 0
|
||||
this.createTime = new Date()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到新的ipList
|
||||
* @param ipList
|
||||
*/
|
||||
setIpList (ipList) {
|
||||
this.ip = ipList.shift()
|
||||
this.ipList = ipList
|
||||
this.lookupCount++
|
||||
this.doCount(this.ip, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 换下一个ip
|
||||
* @param count
|
||||
*/
|
||||
changeNext (count) {
|
||||
count.keepErrorCount = 0 // 清空连续失败
|
||||
if (this.ipList > 0) {
|
||||
this.ip = this.ipList.shift()
|
||||
} else {
|
||||
this.ip = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录使用次数或错误次数
|
||||
* @param ip
|
||||
* @param isError
|
||||
*/
|
||||
doCount (ip, isError) {
|
||||
let count = this.count[ip]
|
||||
if (count == null) {
|
||||
count = this.count[ip] = { total: 0, error: 0, keepErrorCount: 0, successRate: 0 }
|
||||
}
|
||||
if (isError) {
|
||||
count.error++
|
||||
count.keepErrorCount++
|
||||
} else {
|
||||
count.total++
|
||||
}
|
||||
count.successRate = 1 - (count.error / count.total)
|
||||
if (isError && this.ip === ip) {
|
||||
if (count.keepErrorCount >= 5) {
|
||||
this.changeNext(count)
|
||||
}
|
||||
if (count.successRate < 0.51) {
|
||||
this.changeNext(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = class BaseDNS {
|
||||
|
@ -13,30 +74,39 @@ module.exports = class BaseDNS {
|
|||
this.cache = new LRU(cacheSize)
|
||||
}
|
||||
|
||||
count (hostname, ip, isError = true) {
|
||||
const ipCache = this.cache.get(hostname)
|
||||
if (ipCache) {
|
||||
ipCache.doCount(ip, isError)
|
||||
}
|
||||
}
|
||||
|
||||
async lookup (hostname) {
|
||||
try {
|
||||
let ip = this.cache.get(hostname)
|
||||
if (ip) {
|
||||
return ip
|
||||
let ipCache = this.cache.get(hostname)
|
||||
if (ipCache) {
|
||||
if (ipCache.ip != null) {
|
||||
ipCache.doCount(ipCache.ip, false)
|
||||
return ipCache.ip
|
||||
}
|
||||
} else {
|
||||
ipCache = new IpCache(hostname)
|
||||
this.cache.set(hostname, ipCache)
|
||||
}
|
||||
|
||||
const t = new Date()
|
||||
|
||||
ip = hostname
|
||||
for (let depth = 0; !_isIP(ip) && depth < 5; depth++) {
|
||||
ip = await this._lookup(ip).catch(error => {
|
||||
logger.debug(ip, error)
|
||||
return ip
|
||||
})
|
||||
let ipList = await this._lookup(hostname)
|
||||
if (ipList == null) {
|
||||
// 没有获取到ipv4地址
|
||||
ipList = []
|
||||
}
|
||||
ipList.push(hostname) // 把原域名加入到统计里去
|
||||
|
||||
if (!_isIP(ip)) {
|
||||
throw new Error(`BAD IP FORMAT (${ip})`)
|
||||
}
|
||||
ipCache.setIpList(ipList)
|
||||
|
||||
logger.debug(`[DNS] ${hostname} -> ${ip} (${new Date() - t} ms)`)
|
||||
this.cache.set(hostname, ip)
|
||||
return ip
|
||||
logger.debug(`[DNS] ${hostname} -> ${ipCache.ip} (${new Date() - t} ms)`)
|
||||
|
||||
return ipCache.ip
|
||||
} catch (error) {
|
||||
logger.debug(`[DNS] cannot resolve hostname ${hostname} (${error})`)
|
||||
return hostname
|
||||
|
|
|
@ -12,6 +12,17 @@ module.exports = class DNSOverHTTPS extends BaseDNS {
|
|||
|
||||
async _lookup (hostname) {
|
||||
const result = await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
|
||||
return result.answers[0].data
|
||||
if (result.answers.length === 0) {
|
||||
// 说明没有获取到ip
|
||||
console.log('该域名没有ip地址解析', hostname)
|
||||
return []
|
||||
}
|
||||
const ret = result.answers.filter(item => { return item.type === 'A' }).map(item => { return item.data })
|
||||
if (ret.length === 0) {
|
||||
console.log('该域名没有ipv4地址解析', hostname)
|
||||
} else {
|
||||
console.log('获取到域名地址:', hostname, JSON.stringify(ret))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
if (providerName) {
|
||||
console.log('匹配到dns:', providerName, hostname)
|
||||
return dnsConfig.providers[providerName]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ const tunnelAgent = require('tunnel-agent')
|
|||
const util = exports
|
||||
const httpsAgent = new HttpsAgent({
|
||||
keepAlive: true,
|
||||
timeout: 5000,
|
||||
timeout: 15000,
|
||||
keepAliveTimeout: 60000, // free socket keepalive for 30 seconds
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
const httpAgent = new Agent({
|
||||
keepAlive: true,
|
||||
timeout: 5000,
|
||||
timeout: 15000,
|
||||
keepAliveTimeout: 60000 // free socket keepalive for 30 seconds
|
||||
})
|
||||
let socketId = 0
|
||||
|
@ -34,7 +34,7 @@ util.getOptionsFormRequest = (req, ssl, externalProxy = null) => {
|
|||
try {
|
||||
externalProxyUrl = externalProxy(req, ssl)
|
||||
} catch (e) {
|
||||
console.error('externalProxy',e)
|
||||
console.error('externalProxy', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ module.exports = function createConnectHandler (sslConnectInterceptor, fakeServe
|
|||
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
|
||||
if (dns) {
|
||||
dns.lookup(hostname).then(ip => {
|
||||
connect(req, cltSocket, head, ip, srvUrl.port)
|
||||
connect(req, cltSocket, head, ip, srvUrl.port, { dns, hostname, ip })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ module.exports = function createConnectHandler (sslConnectInterceptor, fakeServe
|
|||
}
|
||||
}
|
||||
|
||||
function connect (req, cltSocket, head, hostname, port) {
|
||||
function connect (req, cltSocket, head, hostname, port, isDnsIntercept) {
|
||||
// tunneling https
|
||||
// console.log('connect:', hostname, port)
|
||||
const start = new Date().getTime()
|
||||
|
@ -57,6 +57,12 @@ function connect (req, cltSocket, head, hostname, port) {
|
|||
const end = new Date().getTime()
|
||||
console.error('代理连接失败:', e.message, hostname, port, (end - start) + 'ms')
|
||||
cltSocket.destroy()
|
||||
|
||||
if (isDnsIntercept) {
|
||||
const { dns, ip, hostname } = isDnsIntercept
|
||||
dns.count(hostname, ip, true)
|
||||
console.error('记录ip失败次数,用于优选ip:', hostname, ip)
|
||||
}
|
||||
})
|
||||
return proxySocket
|
||||
} catch (error) {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import dns from '../src/lib/dns/index.js'
|
||||
|
||||
const dnsProviders = dns.initDNS({
|
||||
aliyun: {
|
||||
type: 'https',
|
||||
server: 'https://dns.alidns.com/dns-query',
|
||||
cacheSize: 1000
|
||||
},
|
||||
usa: {
|
||||
type: 'https',
|
||||
server: 'https://cloudflare-dns.com/dns-query',
|
||||
cacheSize: 1000
|
||||
}
|
||||
})
|
||||
|
||||
// let hostname = 'www.yonsz.com'
|
||||
// dnsProviders.usa.lookup(hostname)
|
||||
|
||||
// const hostname = 'api.github.com'
|
||||
// dnsProviders.usa.lookup(hostname)
|
||||
|
||||
const hostname1 = 'api.github.com'
|
||||
dnsProviders.aliyun.lookup(hostname1)
|
Loading…
Reference in New Issue