refactor: dns优选
parent
344c732ad6
commit
dfd2db5285
|
@ -24,7 +24,7 @@ module.exports = {
|
||||||
'.*': { proxy: 'raw.fastgit.org' }
|
'.*': { proxy: 'raw.fastgit.org' }
|
||||||
},
|
},
|
||||||
'github.githubassets.com': {
|
'github.githubassets.com': {
|
||||||
'.*': { proxy: 'assets.fastgit.org' }
|
'.*': { proxy: 'assets.fastgit.org', test: 'https://github.githubassets.com/favicons/favicon.svg' }
|
||||||
},
|
},
|
||||||
'customer-stories-feed.github.com': {
|
'customer-stories-feed.github.com': {
|
||||||
'.*': { proxy: 'customer-stories-feed.fastgit.org' }
|
'.*': { proxy: 'customer-stories-feed.fastgit.org' }
|
||||||
|
|
|
@ -8,7 +8,7 @@ process.on('uncaughtException', function (err) {
|
||||||
// console.error(err.errno)
|
// console.error(err.errno)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.error('uncaughtException',err)
|
console.error('uncaughtException', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
|
|
@ -137,6 +137,7 @@ const NodePlugin = function (context) {
|
||||||
]
|
]
|
||||||
const ret = await shell.exec(cmds, { type: 'cmd' })
|
const ret = await shell.exec(cmds, { type: 'cmd' })
|
||||||
event.fire('status', { key: 'plugin.node.enabled', value: false })
|
event.fire('status', { key: 'plugin.node.enabled', value: false })
|
||||||
|
console.info('关闭【NPM】代理成功')
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,9 +97,9 @@ const serverApi = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async restart () {
|
async restart ({ mitmproxyPath }) {
|
||||||
await serverApi.kill()
|
await serverApi.kill()
|
||||||
await serverApi.start()
|
await serverApi.start({ mitmproxyPath })
|
||||||
},
|
},
|
||||||
getServer () {
|
getServer () {
|
||||||
return server
|
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)
|
const appTray = new Tray(iconPath)
|
||||||
|
|
||||||
// 图标的上下文菜单
|
// 图标的上下文菜单
|
||||||
|
@ -44,14 +44,25 @@ function setTray (app) {
|
||||||
// 设置托盘悬浮提示
|
// 设置托盘悬浮提示
|
||||||
appTray.setToolTip('DevSidecar-开发者边车辅助工具')
|
appTray.setToolTip('DevSidecar-开发者边车辅助工具')
|
||||||
|
|
||||||
// 设置托盘菜单
|
|
||||||
appTray.setContextMenu(contextMenu)
|
|
||||||
|
|
||||||
// 单击托盘小图标显示应用
|
// 单击托盘小图标显示应用
|
||||||
appTray.on('click', () => {
|
appTray.on('click', () => {
|
||||||
// 显示主程序
|
// 显示主程序
|
||||||
win.show()
|
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
|
return appTray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,9 @@ const localApi = {
|
||||||
server: {
|
server: {
|
||||||
start () {
|
start () {
|
||||||
return DevSidecar.api.server.start({ mitmproxyPath })
|
return DevSidecar.api.server.start({ mitmproxyPath })
|
||||||
|
},
|
||||||
|
restart () {
|
||||||
|
return DevSidecar.api.server.restart({ mitmproxyPath })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
|
|
@ -22,7 +22,7 @@ function register (app) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleServerStartError (err, app) {
|
function handleServerStartError (err, app) {
|
||||||
if (err.message.indexOf('listen EADDRINUSE') >= 0) {
|
if (err.message && err.message.indexOf('listen EADDRINUSE') >= 0) {
|
||||||
app.$confirm({
|
app.$confirm({
|
||||||
title: '端口被占用,代理服务启动失败',
|
title: '端口被占用,代理服务启动失败',
|
||||||
content: '是否要杀掉占用进程?',
|
content: '是否要杀掉占用进程?',
|
||||||
|
|
|
@ -10,7 +10,8 @@ export default {
|
||||||
config: undefined,
|
config: undefined,
|
||||||
status: status,
|
status: status,
|
||||||
labelCol: { span: 4 },
|
labelCol: { span: 4 },
|
||||||
wrapperCol: { span: 20 }
|
wrapperCol: { span: 20 },
|
||||||
|
applyLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
@ -27,12 +28,17 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
apply () {
|
async apply () {
|
||||||
return this.saveConfig().then(() => {
|
this.applyLoading = true
|
||||||
if (this.applyAfter) {
|
await this.applyBefore()
|
||||||
return this.applyAfter()
|
await this.saveConfig()
|
||||||
}
|
if (this.applyAfter) {
|
||||||
})
|
await this.applyAfter()
|
||||||
|
}
|
||||||
|
this.applyLoading = false
|
||||||
|
},
|
||||||
|
async applyBefore () {
|
||||||
|
|
||||||
},
|
},
|
||||||
reloadDefault (key) {
|
reloadDefault (key) {
|
||||||
this.$api.config.resetDefault(key).then(ret => {
|
this.$api.config.resetDefault(key).then(ret => {
|
||||||
|
|
|
@ -59,8 +59,8 @@
|
||||||
</div>
|
</div>
|
||||||
<template slot="footer">
|
<template slot="footer">
|
||||||
<div class="footer-bar">
|
<div class="footer-bar">
|
||||||
<a-button class="md-mr-10" @click="reloadDefault('plugin.node')">恢复默认</a-button>
|
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')">恢复默认</a-button>
|
||||||
<a-button type="primary" @click="apply()">应用</a-button>
|
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()">应用</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ds-container>
|
</ds-container>
|
||||||
|
@ -91,13 +91,13 @@ export default {
|
||||||
this.npmVariables = ret
|
this.npmVariables = ret
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSwitchRegistry (event) {
|
async onSwitchRegistry (event) {
|
||||||
return this.setRegistry(event.target.value).then(() => {
|
await this.setRegistry(event.target.value)
|
||||||
this.$message.success('切换成功')
|
this.$message.success('切换成功')
|
||||||
})
|
|
||||||
},
|
},
|
||||||
setRegistry (registry) {
|
async setRegistry (registry) {
|
||||||
return this.$api.plugin.node.setRegistry(registry)
|
this.apply()
|
||||||
|
await this.$api.plugin.node.setRegistry(registry)
|
||||||
},
|
},
|
||||||
setNpmVariableAll () {
|
setNpmVariableAll () {
|
||||||
this.saveConfig().then(() => {
|
this.saveConfig().then(() => {
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
</div>
|
</div>
|
||||||
<template slot="footer">
|
<template slot="footer">
|
||||||
<div class="footer-bar">
|
<div class="footer-bar">
|
||||||
<a-button class="md-mr-10" @click="reloadDefault('proxy')">恢复默认</a-button>
|
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')">恢复默认</a-button>
|
||||||
<a-button type="primary" @click="apply()">应用</a-button>
|
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()">应用</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ds-container>
|
</ds-container>
|
||||||
|
|
|
@ -69,8 +69,8 @@
|
||||||
</div>
|
</div>
|
||||||
<template slot="footer">
|
<template slot="footer">
|
||||||
<div class="footer-bar">
|
<div class="footer-bar">
|
||||||
<a-button class="md-mr-10" @click="reloadDefault('server')">恢复默认</a-button>
|
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')">恢复默认</a-button>
|
||||||
<a-button type="primary" @click="apply()">应用</a-button>
|
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()">应用</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ds-container>
|
</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 LRU = require('lru-cache')
|
||||||
const { isIP } = require('validator')
|
// const { isIP } = require('validator')
|
||||||
const getLogger = require('../utils/logger')
|
const getLogger = require('../utils/logger')
|
||||||
|
|
||||||
const logger = getLogger('dns')
|
const logger = getLogger('dns')
|
||||||
const cacheSize = 1024
|
const cacheSize = 1024
|
||||||
function _isIP (v) {
|
// eslint-disable-next-line no-unused-vars
|
||||||
return v && isIP(v)
|
// 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 {
|
module.exports = class BaseDNS {
|
||||||
|
@ -13,30 +74,39 @@ module.exports = class BaseDNS {
|
||||||
this.cache = new LRU(cacheSize)
|
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) {
|
async lookup (hostname) {
|
||||||
try {
|
try {
|
||||||
let ip = this.cache.get(hostname)
|
let ipCache = this.cache.get(hostname)
|
||||||
if (ip) {
|
if (ipCache) {
|
||||||
return ip
|
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()
|
const t = new Date()
|
||||||
|
let ipList = await this._lookup(hostname)
|
||||||
ip = hostname
|
if (ipList == null) {
|
||||||
for (let depth = 0; !_isIP(ip) && depth < 5; depth++) {
|
// 没有获取到ipv4地址
|
||||||
ip = await this._lookup(ip).catch(error => {
|
ipList = []
|
||||||
logger.debug(ip, error)
|
|
||||||
return ip
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
ipList.push(hostname) // 把原域名加入到统计里去
|
||||||
|
|
||||||
if (!_isIP(ip)) {
|
ipCache.setIpList(ipList)
|
||||||
throw new Error(`BAD IP FORMAT (${ip})`)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`[DNS] ${hostname} -> ${ip} (${new Date() - t} ms)`)
|
logger.debug(`[DNS] ${hostname} -> ${ipCache.ip} (${new Date() - t} ms)`)
|
||||||
this.cache.set(hostname, ip)
|
|
||||||
return ip
|
return ipCache.ip
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(`[DNS] cannot resolve hostname ${hostname} (${error})`)
|
logger.debug(`[DNS] cannot resolve hostname ${hostname} (${error})`)
|
||||||
return hostname
|
return hostname
|
||||||
|
|
|
@ -12,6 +12,17 @@ module.exports = class DNSOverHTTPS extends BaseDNS {
|
||||||
|
|
||||||
async _lookup (hostname) {
|
async _lookup (hostname) {
|
||||||
const result = await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: 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) {
|
if (providerName) {
|
||||||
console.log('匹配到dns:', providerName, hostname)
|
|
||||||
return dnsConfig.providers[providerName]
|
return dnsConfig.providers[providerName]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ const tunnelAgent = require('tunnel-agent')
|
||||||
const util = exports
|
const util = exports
|
||||||
const httpsAgent = new HttpsAgent({
|
const httpsAgent = new HttpsAgent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
timeout: 5000,
|
timeout: 15000,
|
||||||
keepAliveTimeout: 60000, // free socket keepalive for 30 seconds
|
keepAliveTimeout: 60000, // free socket keepalive for 30 seconds
|
||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
})
|
})
|
||||||
const httpAgent = new Agent({
|
const httpAgent = new Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
timeout: 5000,
|
timeout: 15000,
|
||||||
keepAliveTimeout: 60000 // free socket keepalive for 30 seconds
|
keepAliveTimeout: 60000 // free socket keepalive for 30 seconds
|
||||||
})
|
})
|
||||||
let socketId = 0
|
let socketId = 0
|
||||||
|
@ -34,7 +34,7 @@ util.getOptionsFormRequest = (req, ssl, externalProxy = null) => {
|
||||||
try {
|
try {
|
||||||
externalProxyUrl = externalProxy(req, ssl)
|
externalProxyUrl = externalProxy(req, ssl)
|
||||||
} catch (e) {
|
} 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)
|
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
|
||||||
if (dns) {
|
if (dns) {
|
||||||
dns.lookup(hostname).then(ip => {
|
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
|
// tunneling https
|
||||||
// console.log('connect:', hostname, port)
|
// console.log('connect:', hostname, port)
|
||||||
const start = new Date().getTime()
|
const start = new Date().getTime()
|
||||||
|
@ -57,6 +57,12 @@ function connect (req, cltSocket, head, hostname, port) {
|
||||||
const end = new Date().getTime()
|
const end = new Date().getTime()
|
||||||
console.error('代理连接失败:', e.message, hostname, port, (end - start) + 'ms')
|
console.error('代理连接失败:', e.message, hostname, port, (end - start) + 'ms')
|
||||||
cltSocket.destroy()
|
cltSocket.destroy()
|
||||||
|
|
||||||
|
if (isDnsIntercept) {
|
||||||
|
const { dns, ip, hostname } = isDnsIntercept
|
||||||
|
dns.count(hostname, ip, true)
|
||||||
|
console.error('记录ip失败次数,用于优选ip:', hostname, ip)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return proxySocket
|
return proxySocket
|
||||||
} catch (error) {
|
} 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