1)feature: IP测速功能,针对请求的端口进行测速,不再固定 `443` 端口进行测速;
2)bugfix: IP测速功能,域名长时间未访问时,再访问后不会重启自动测速功能的问题修复; 3)optimize: 优化DNS获取的IP再经过IpTester校验的逻辑。develop
parent
ab74a44e83
commit
ab76d03f0b
|
@ -455,9 +455,9 @@ export default {
|
|||
</a>
|
||||
<a-tag
|
||||
v-for="(element, index) of item.backupList" :key="index" style="margin:2px;"
|
||||
:title="element.dns" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : 'red'"
|
||||
:title="element.title || `测速中:${element.host}`" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : (element.title ? 'red' : '')"
|
||||
>
|
||||
{{ element.host }} {{ element.time }}{{ element.time ? 'ms' : '' }} {{ element.dns }}
|
||||
{{ element.host }} {{ element.time ? `${element.time}ms` : (element.title ? '' : '测速中') }} {{ element.dns }}
|
||||
</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
|
|
@ -152,6 +152,12 @@ $dark-input: #777; //输入框:背景色
|
|||
border-color: #5a5750;
|
||||
color: #cfa572;
|
||||
}
|
||||
/* 标签:未知 */
|
||||
.ant-tag:not(.ant-tag-red, .ant-tag-green, .ant-tag-orange) {
|
||||
background-color: #5a5a5a;
|
||||
border-color: #5a5a5a;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.ant-btn:not(.ant-btn-danger, .ant-btn-primary) {
|
||||
|
|
|
@ -52,13 +52,18 @@ module.exports = class BaseDNS {
|
|||
}
|
||||
}
|
||||
|
||||
async lookup (hostname) {
|
||||
async lookup (hostname, ipChecker) {
|
||||
try {
|
||||
let ipCache = this.cache.get(hostname)
|
||||
if (ipCache) {
|
||||
if (ipCache.value != null) {
|
||||
ipCache.doCount(ipCache.value, false)
|
||||
return ipCache.value
|
||||
const ip = ipCache.value
|
||||
if (ip != null) {
|
||||
if (ipChecker && ipChecker(ip)) {
|
||||
ipCache.doCount(ip, false)
|
||||
return ip
|
||||
} else {
|
||||
return hostname
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ipCache = new IpCache(hostname)
|
||||
|
@ -66,7 +71,7 @@ module.exports = class BaseDNS {
|
|||
}
|
||||
|
||||
const t = new Date()
|
||||
let ipList = await this._lookupInternal(hostname)
|
||||
let ipList = await this._lookupWithPreSetIpList(hostname)
|
||||
if (ipList == null) {
|
||||
// 没有获取到ipv4地址
|
||||
ipList = []
|
||||
|
@ -74,28 +79,42 @@ module.exports = class BaseDNS {
|
|||
ipList.push(hostname) // 把原域名加入到统计里去
|
||||
|
||||
ipCache.setBackupList(ipList)
|
||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname} ➜ ${ipCache.value} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
|
||||
|
||||
return ipCache.value
|
||||
const ip = ipCache.value
|
||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname} ➜ ${ip} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
|
||||
|
||||
if (ipChecker) {
|
||||
if (ip != null && ip !== hostname && ipChecker(ip)) {
|
||||
return ip
|
||||
}
|
||||
|
||||
for (const ip of ipList) {
|
||||
if (ip !== hostname && ipChecker(ip)) {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ip != null ? ip : hostname
|
||||
} catch (error) {
|
||||
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error)
|
||||
return hostname
|
||||
}
|
||||
}
|
||||
|
||||
async _lookupInternal (hostname) {
|
||||
async _lookupWithPreSetIpList (hostname) {
|
||||
// 获取当前域名的预设IP列表
|
||||
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`)
|
||||
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
|
||||
if (hostnamePreSetIpList.length > 0) {
|
||||
hostnamePreSetIpList = hostnamePreSetIpList.slice()
|
||||
hostnamePreSetIpList = hostnamePreSetIpList.slice() // 复制一份列表数据,避免配置数据被覆盖
|
||||
} else {
|
||||
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
|
||||
}
|
||||
|
||||
if (hostnamePreSetIpList.length > 0) {
|
||||
hostnamePreSetIpList.isPreSet = true
|
||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
|
||||
log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
|
||||
return hostnamePreSetIpList
|
||||
}
|
||||
}
|
||||
|
@ -106,23 +125,59 @@ module.exports = class BaseDNS {
|
|||
async _lookup (hostname) {
|
||||
const start = Date.now()
|
||||
try {
|
||||
// 执行DNS查询
|
||||
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query start: ${hostname}`)
|
||||
const response = await this._doDnsQuery(hostname)
|
||||
const cost = Date.now() - start
|
||||
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query end: ${hostname}, cost: ${cost} ms, response:`, response)
|
||||
|
||||
if (response == null || response.answers == null || response.answers.length == null || response.answers.length === 0) {
|
||||
// 说明没有获取到ip
|
||||
log.warn(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response)
|
||||
return []
|
||||
}
|
||||
|
||||
const ret = response.answers.filter(item => item.type === 'A').map(item => item.data)
|
||||
if (ret.length === 0) {
|
||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`)
|
||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`)
|
||||
} else {
|
||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
|
||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IP地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
|
||||
}
|
||||
|
||||
return ret
|
||||
} catch (e) {
|
||||
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS query error, hostname: ${hostname}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
_doDnsQuery (hostname, type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 设置超时任务
|
||||
let isOver = false
|
||||
const timeout = 6000
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (!isOver) {
|
||||
reject(new Error('DNS查询超时'))
|
||||
}
|
||||
}, timeout)
|
||||
|
||||
try {
|
||||
this._dnsQueryPromise(hostname, type)
|
||||
.then((response) => {
|
||||
isOver = true
|
||||
clearTimeout(timeoutId)
|
||||
resolve(response)
|
||||
})
|
||||
.catch((e) => {
|
||||
isOver = true
|
||||
clearTimeout(timeoutId)
|
||||
reject(e)
|
||||
})
|
||||
} catch (e) {
|
||||
isOver = true
|
||||
clearTimeout(timeoutId)
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ module.exports = class DNSOverHTTPS extends BaseDNS {
|
|||
this.dnsServer = dnsServer
|
||||
}
|
||||
|
||||
async _doDnsQuery (hostname) {
|
||||
return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
|
||||
_dnsQueryPromise (hostname, type = 'A') {
|
||||
return dohQueryAsync({ url: this.dnsServer }, [{ type, name: hostname }])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = class DNSOverTCP extends BaseDNS {
|
|||
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
||||
}
|
||||
|
||||
_doDnsQuery (hostname) {
|
||||
_dnsQueryPromise (hostname, type = 'A') {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 构造 DNS 查询报文
|
||||
const packet = dnsPacket.encode({
|
||||
|
@ -21,7 +21,7 @@ module.exports = class DNSOverTCP extends BaseDNS {
|
|||
type: 'query',
|
||||
id: randi(0x0, 0xFFFF),
|
||||
questions: [{
|
||||
type: 'A',
|
||||
type,
|
||||
name: hostname,
|
||||
}],
|
||||
})
|
||||
|
|
|
@ -11,7 +11,7 @@ module.exports = class DNSOverTLS extends BaseDNS {
|
|||
this.dnsServerName = dnsServerName
|
||||
}
|
||||
|
||||
async _doDnsQuery (hostname) {
|
||||
_dnsQueryPromise (hostname, type = 'A') {
|
||||
const options = {
|
||||
host: this.dnsServer,
|
||||
port: this.dnsServerPort,
|
||||
|
@ -19,9 +19,9 @@ module.exports = class DNSOverTLS extends BaseDNS {
|
|||
|
||||
name: hostname,
|
||||
klass: 'IN',
|
||||
type: 'A',
|
||||
type,
|
||||
}
|
||||
|
||||
return await dnstls.query(options)
|
||||
return dnstls.query(options)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,21 +15,28 @@ module.exports = class DNSOverUDP extends BaseDNS {
|
|||
this.socketType = this.isIPv6 ? 'udp6' : 'udp4'
|
||||
}
|
||||
|
||||
_doDnsQuery (hostname) {
|
||||
_dnsQueryPromise (hostname, type = 'A') {
|
||||
return new Promise((resolve, reject) => {
|
||||
let isOver = false
|
||||
const timeout = 5000
|
||||
let timeoutId = null
|
||||
|
||||
// 构造 DNS 查询报文
|
||||
const packet = dnsPacket.encode({
|
||||
flags: dnsPacket.RECURSION_DESIRED,
|
||||
type: 'query',
|
||||
id: randi(0x0, 0xFFFF),
|
||||
questions: [{
|
||||
type: 'A',
|
||||
type,
|
||||
name: hostname,
|
||||
}],
|
||||
})
|
||||
|
||||
// 创建客户端
|
||||
const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => {
|
||||
isOver = true
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
const response = dnsPacket.decode(msg)
|
||||
resolve(response)
|
||||
udpClient.close()
|
||||
|
@ -38,10 +45,20 @@ module.exports = class DNSOverUDP extends BaseDNS {
|
|||
// 发送 UDP 查询
|
||||
udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => {
|
||||
if (err) {
|
||||
isOver = true
|
||||
clearTimeout(timeoutId)
|
||||
reject(err)
|
||||
udpClient.close()
|
||||
}
|
||||
})
|
||||
|
||||
// 设置超时任务
|
||||
timeoutId = setTimeout(() => {
|
||||
if (!isOver) {
|
||||
reject(new Error('查询超时'))
|
||||
udpClient.close()
|
||||
}
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
|
|||
if (dnsConfig && dnsConfig.dnsMap) {
|
||||
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
|
||||
if (dns) {
|
||||
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, isDnsIntercept)
|
||||
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, port, isDnsIntercept)
|
||||
}
|
||||
}
|
||||
// 代理连接事件监听
|
||||
|
|
|
@ -124,7 +124,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
|||
}
|
||||
}
|
||||
if (dns) {
|
||||
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, isDnsIntercept)
|
||||
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, rOptions.port, isDnsIntercept)
|
||||
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.dnsName}`)
|
||||
res.setHeader('DS-DNS', dns.dnsName)
|
||||
} else {
|
||||
|
|
|
@ -2,12 +2,35 @@ const defaultDns = require('node:dns')
|
|||
const log = require('../../../utils/util.log.server')
|
||||
const speedTest = require('../../speed')
|
||||
|
||||
function createIpChecker (tester) {
|
||||
if (!tester || tester.backupList == null || tester.backupList.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (ip) => {
|
||||
for (let i = 0; i < tester.backupList.length; i++) {
|
||||
const item = tester.backupList[i]
|
||||
if (item.host === ip) {
|
||||
if (item.time > 0) {
|
||||
return true // IP测速成功
|
||||
}
|
||||
if (item.status === 'failed') {
|
||||
return false // IP测速失败
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return true // IP测速未知
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createLookupFunc (res, dns, action, target, isDnsIntercept) {
|
||||
createLookupFunc (res, dns, action, target, port, isDnsIntercept) {
|
||||
target = target ? (`, target: ${target}`) : ''
|
||||
|
||||
return (hostname, options, callback) => {
|
||||
const tester = speedTest.getSpeedTester(hostname)
|
||||
const tester = speedTest.getSpeedTester(hostname, port)
|
||||
if (tester) {
|
||||
const aliveIpObj = tester.pickFastAliveIpObj()
|
||||
if (aliveIpObj) {
|
||||
|
@ -21,7 +44,10 @@ module.exports = {
|
|||
log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
|
||||
}
|
||||
}
|
||||
dns.lookup(hostname).then((ip) => {
|
||||
|
||||
const ipChecker = createIpChecker(tester)
|
||||
|
||||
dns.lookup(hostname, ipChecker).then((ip) => {
|
||||
if (isDnsIntercept) {
|
||||
isDnsIntercept.dns = dns
|
||||
isDnsIntercept.hostname = hostname
|
||||
|
@ -29,35 +55,16 @@ module.exports = {
|
|||
}
|
||||
|
||||
if (ip !== hostname) {
|
||||
// 判断是否为测速失败的IP,如果是,则不使用当前IP
|
||||
let isTestFailedIp = false
|
||||
if (tester && tester.ready && tester.backupList && tester.backupList.length > 0) {
|
||||
for (let i = 0; i < tester.backupList.length; i++) {
|
||||
const item = tester.backupList[i]
|
||||
if (item.host === ip) {
|
||||
if (item.time == null) {
|
||||
isTestFailedIp = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isTestFailedIp === false) {
|
||||
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`)
|
||||
if (res) {
|
||||
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
|
||||
}
|
||||
callback(null, ip, 4)
|
||||
return
|
||||
} else {
|
||||
// 使用默认dns
|
||||
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options)
|
||||
}
|
||||
} else {
|
||||
// 使用默认dns
|
||||
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
|
||||
}
|
||||
log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
|
||||
defaultDns.lookup(hostname, options, callback)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,31 +1,44 @@
|
|||
// 1个小时不访问,取消获取
|
||||
// const { exec } = require('node:child_process')
|
||||
const net = require('node:net')
|
||||
const _ = require('lodash')
|
||||
const log = require('../../utils/util.log.server')
|
||||
const config = require('./config.js')
|
||||
|
||||
// const isWindows = process.platform === 'win32'
|
||||
|
||||
const DISABLE_TIMEOUT = 60 * 60 * 1000
|
||||
|
||||
class SpeedTester {
|
||||
constructor ({ hostname }) {
|
||||
constructor ({ hostname, port }) {
|
||||
this.dnsMap = config.getConfig().dnsMap
|
||||
|
||||
this.hostname = hostname
|
||||
this.lastReadTime = Date.now()
|
||||
this.port = port || 443
|
||||
|
||||
this.ready = false
|
||||
this.alive = []
|
||||
this.backupList = []
|
||||
this.keepCheckId = false
|
||||
|
||||
this.loadingIps = false
|
||||
this.loadingTest = false
|
||||
|
||||
this.testCount = 0
|
||||
this.test()
|
||||
this.lastReadTime = Date.now()
|
||||
this.keepCheckIntervalId = false
|
||||
|
||||
this.tryTestCount = 0
|
||||
|
||||
this.test() // 异步:初始化完成后先测速一次
|
||||
}
|
||||
|
||||
pickFastAliveIpObj () {
|
||||
this.touch()
|
||||
|
||||
if (this.alive.length === 0) {
|
||||
if (this.backupList.length > 0 && this.tryTestCount % 10 > 0) {
|
||||
this.testBackups() // 异步
|
||||
} else if (this.tryTestCount % 10 === 0) {
|
||||
this.test() // 异步
|
||||
}
|
||||
this.tryTestCount++
|
||||
|
||||
return null
|
||||
}
|
||||
return this.alive[0]
|
||||
|
@ -33,26 +46,27 @@ class SpeedTester {
|
|||
|
||||
touch () {
|
||||
this.lastReadTime = Date.now()
|
||||
if (!this.keepCheckId) {
|
||||
if (!this.keepCheckIntervalId) {
|
||||
this.startChecker()
|
||||
}
|
||||
}
|
||||
|
||||
startChecker () {
|
||||
if (this.keepCheckId) {
|
||||
clearInterval(this.keepCheckId)
|
||||
if (this.keepCheckIntervalId) {
|
||||
clearInterval(this.keepCheckIntervalId)
|
||||
}
|
||||
this.keepCheckId = setInterval(() => {
|
||||
this.keepCheckIntervalId = setInterval(() => {
|
||||
if (Date.now() - DISABLE_TIMEOUT > this.lastReadTime) {
|
||||
// 超过很长时间没有访问,取消测试
|
||||
clearInterval(this.keepCheckId)
|
||||
clearInterval(this.keepCheckIntervalId)
|
||||
this.keepCheckIntervalId = false
|
||||
return
|
||||
}
|
||||
if (this.alive.length > 0) {
|
||||
this.testBackups()
|
||||
return
|
||||
this.testBackups() // 异步
|
||||
} else {
|
||||
this.test() // 异步
|
||||
}
|
||||
this.test()
|
||||
}, config.getConfig().interval)
|
||||
}
|
||||
|
||||
|
@ -71,44 +85,56 @@ class SpeedTester {
|
|||
promiseList.push(one)
|
||||
}
|
||||
await Promise.all(promiseList)
|
||||
|
||||
const items = []
|
||||
for (const ip in ips) {
|
||||
items.push({ host: ip, port: 443, dns: ips[ip].dns })
|
||||
items.push({ host: ip, dns: ips[ip].dns })
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
async getFromOneDns (dns) {
|
||||
return await dns._lookupInternal(this.hostname)
|
||||
return await dns._lookupWithPreSetIpList(this.hostname)
|
||||
}
|
||||
|
||||
async test () {
|
||||
this.testCount++
|
||||
log.debug(`[speed] test start: ${this.hostname}, testCount: ${this.testCount}`)
|
||||
|
||||
try {
|
||||
const newList = await this.getIpListFromDns(this.dnsMap)
|
||||
const newBackupList = [...newList, ...this.backupList]
|
||||
this.backupList = _.unionBy(newBackupList, 'host')
|
||||
this.testCount++
|
||||
|
||||
log.info('[speed]', this.hostname, '➜ ip-list:', this.backupList)
|
||||
await this.testBackups()
|
||||
log.info(`[speed] test end: ${this.hostname} ➜ ip-list:`, this.backupList, `, testCount: ${this.testCount}`)
|
||||
if (config.notify) {
|
||||
config.notify({ key: 'test' })
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(`[speed] test failed: ${this.hostname}, testCount: ${this.testCount}, error:`, e)
|
||||
}
|
||||
}
|
||||
|
||||
async testBackups () {
|
||||
const testAll = []
|
||||
if (this.backupList.length > 0) {
|
||||
const aliveList = []
|
||||
|
||||
const testAll = []
|
||||
for (const item of this.backupList) {
|
||||
testAll.push(this.doTest(item, aliveList))
|
||||
}
|
||||
await Promise.all(testAll)
|
||||
this.alive = aliveList
|
||||
}
|
||||
|
||||
this.ready = true
|
||||
}
|
||||
|
||||
async doTest (item, aliveList) {
|
||||
try {
|
||||
const ret = await this.testOne(item)
|
||||
item.title = `${ret.by}测速成功:${item.host}`
|
||||
log.info(`[speed] test success: ${this.hostname} ➜ ${item.host}:${this.port} from DNS '${item.dns}'`)
|
||||
_.merge(item, ret)
|
||||
aliveList.push({ ...ret, ...item })
|
||||
aliveList.sort((a, b) => a.time - b.time)
|
||||
|
@ -125,48 +151,131 @@ class SpeedTester {
|
|||
return a.time - b.time
|
||||
})
|
||||
} catch (e) {
|
||||
if (e.message !== 'timeout') {
|
||||
log.warn('[speed] test error: ', this.hostname, `➜ ${item.host}:${item.port} from DNS '${item.dns}'`, ', errorMsg:', e.message)
|
||||
if (item.time == null) {
|
||||
item.title = e.message
|
||||
item.status = 'failed'
|
||||
}
|
||||
if (!e.message.includes('timeout')) {
|
||||
log.warn(`[speed] test error: ${this.hostname} ➜ ${item.host}:${this.port} from DNS '${item.dns}', errorMsg: ${e.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testOne (item) {
|
||||
const timeout = 5000
|
||||
const { host, port, dns } = item
|
||||
const startTime = Date.now()
|
||||
let isOver = false
|
||||
testByTCP (item) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { host, dns } = item
|
||||
const startTime = Date.now()
|
||||
|
||||
let isOver = false
|
||||
const timeout = 5000
|
||||
let timeoutId = null
|
||||
const client = net.createConnection({ host, port }, () => {
|
||||
// 'connect' 监听器
|
||||
const connectionTime = Date.now()
|
||||
|
||||
const client = net.createConnection({ host, port: this.port }, () => {
|
||||
isOver = true
|
||||
clearTimeout(timeoutId)
|
||||
resolve({ status: 'success', time: connectionTime - startTime })
|
||||
|
||||
const connectionTime = Date.now()
|
||||
resolve({ status: 'success', by: 'TCP', time: connectionTime - startTime })
|
||||
client.end()
|
||||
})
|
||||
client.on('end', () => {
|
||||
})
|
||||
client.on('error', (e) => {
|
||||
if (e.message !== 'timeout') {
|
||||
log.warn('[speed] test error: ', this.hostname, `➜ ${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message)
|
||||
}
|
||||
isOver = true
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
log.warn('[speed] test by TCP error: ', this.hostname, `➜ ${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message)
|
||||
reject(e)
|
||||
client.end()
|
||||
})
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
if (isOver) {
|
||||
return
|
||||
}
|
||||
log.warn('[speed] test timeout:', this.hostname, `➜ ${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
|
||||
|
||||
log.warn('[speed] test by TCP timeout:', this.hostname, `➜ ${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
|
||||
reject(new Error('timeout'))
|
||||
client.end()
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
// 暂不使用
|
||||
// testByPing (item) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const { host, dns } = item
|
||||
// const startTime = Date.now()
|
||||
//
|
||||
// // 设置超时程序
|
||||
// let isOver = false
|
||||
// const timeout = 5000
|
||||
// const timeoutId = setTimeout(() => {
|
||||
// if (!isOver) {
|
||||
// log.warn('[speed] test by PING timeout:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
|
||||
// reject(new Error('timeout'))
|
||||
// }
|
||||
// }, timeout)
|
||||
//
|
||||
// // 协议选择(如强制ping6)
|
||||
// const usePing6 = !isWindows && host.includes(':') // Windows无ping6命令
|
||||
// const cmd = usePing6
|
||||
// ? `ping6 -c 2 ${host}`
|
||||
// : isWindows
|
||||
// ? `ping -n 2 ${host}`
|
||||
// : `ping -c 2 ${host}`
|
||||
//
|
||||
// log.debug('[speed] test by PING start:', this.hostname, `➜ ${host} from DNS '${dns}'`)
|
||||
// exec(cmd, (error, stdout, _stderr) => {
|
||||
// isOver = true
|
||||
// clearTimeout(timeoutId)
|
||||
//
|
||||
// if (error) {
|
||||
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 目标不可达或超时`)
|
||||
// reject(new Error('目标不可达或超时'))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 提取延迟数据(正则匹配)
|
||||
// const regex = /[=<](\d+(?:\.\d*)?)ms/gi // 适配Linux/Windows
|
||||
// const times = []
|
||||
// let match
|
||||
// // eslint-disable-next-line no-cond-assign
|
||||
// while ((match = regex.exec(stdout)) !== null) {
|
||||
// times.push(Number.parseFloat(match[1]))
|
||||
// }
|
||||
//
|
||||
// if (times.length === 0) {
|
||||
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 无法解析延迟`)
|
||||
// reject(new Error('无法解析延迟'))
|
||||
// } else {
|
||||
// // 计算平均延迟
|
||||
// const avg = times.reduce((a, b) => a + b, 0) / times.length
|
||||
// resolve({ status: 'success', by: 'PING', time: Math.round(avg) })
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
testOne (item) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const thenFun = (ret) => {
|
||||
resolve(ret)
|
||||
}
|
||||
|
||||
// 先用TCP测速
|
||||
this.testByTCP(item)
|
||||
.then(thenFun)
|
||||
.catch((e) => {
|
||||
// // TCP测速失败,再用 PING 测速
|
||||
// this.testByPing(item)
|
||||
// .then(thenFun)
|
||||
// .catch((e2) => {
|
||||
// reject(new Error(`TCP测速失败:${e.message};PING测速失败:${e2.message};`))
|
||||
// })
|
||||
|
||||
reject(new Error(`TCP测速失败:${e.message}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpeedTester
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const config = {
|
||||
notify () {},
|
||||
dnsMap: {},
|
||||
}
|
||||
module.exports = {
|
||||
getConfig () {
|
||||
return config
|
||||
},
|
||||
notify: null,
|
||||
}
|
||||
|
|
|
@ -6,6 +6,28 @@ const SpeedTester = require('./SpeedTester.js')
|
|||
const SpeedTestPool = {
|
||||
}
|
||||
|
||||
function addSpeedTest (hostname, port) {
|
||||
if (!port) {
|
||||
const idx = hostname.indexOf(':')
|
||||
if (idx > 0 && idx === hostname.lastIndexOf(':')) {
|
||||
const arr = hostname.split(':')
|
||||
hostname = arr[0]
|
||||
port = Number.parseInt(arr[1]) || 443
|
||||
} else {
|
||||
port = 443
|
||||
}
|
||||
}
|
||||
|
||||
// 443端口不拼接在key上
|
||||
const key = port === 443 ? hostname : `${hostname}:${port}`
|
||||
|
||||
if (SpeedTestPool[key] == null) {
|
||||
return SpeedTestPool[key] = new SpeedTester({ hostname, port })
|
||||
}
|
||||
|
||||
return SpeedTestPool[key]
|
||||
}
|
||||
|
||||
function initSpeedTest (runtimeConfig) {
|
||||
const { enabled, hostnameList } = runtimeConfig
|
||||
const conf = config.getConfig()
|
||||
|
@ -14,45 +36,42 @@ function initSpeedTest (runtimeConfig) {
|
|||
return
|
||||
}
|
||||
_.forEach(hostnameList, (hostname) => {
|
||||
SpeedTestPool[hostname] = new SpeedTester({ hostname })
|
||||
addSpeedTest(hostname)
|
||||
})
|
||||
log.info('[speed] enabled')
|
||||
log.info('[speed] enabled,SpeedTestPool:', SpeedTestPool)
|
||||
}
|
||||
|
||||
function getAllSpeedTester () {
|
||||
const allSpeed = {}
|
||||
if (!config.getConfig().enabled) {
|
||||
return allSpeed
|
||||
}
|
||||
|
||||
if (config.getConfig().enabled) {
|
||||
_.forEach(SpeedTestPool, (item, key) => {
|
||||
allSpeed[key] = {
|
||||
hostname: key,
|
||||
hostname: item.hostname,
|
||||
port: item.port,
|
||||
alive: item.alive,
|
||||
backupList: item.backupList,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return allSpeed
|
||||
}
|
||||
|
||||
function getSpeedTester (hostname) {
|
||||
function getSpeedTester (hostname, port) {
|
||||
if (!config.getConfig().enabled) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
let instance = SpeedTestPool[hostname]
|
||||
if (instance == null) {
|
||||
instance = new SpeedTester({ hostname })
|
||||
SpeedTestPool[hostname] = instance
|
||||
}
|
||||
return instance
|
||||
return addSpeedTest(hostname, port)
|
||||
}
|
||||
|
||||
function registerNotify (notify) {
|
||||
config.notify = notify
|
||||
}
|
||||
// function registerNotify (notify) {
|
||||
// config.notify = notify
|
||||
// }
|
||||
|
||||
function reSpeedTest () {
|
||||
_.forEach(SpeedTestPool, (item, key) => {
|
||||
item.test()
|
||||
_.forEach(SpeedTestPool, (item, _key) => {
|
||||
item.test() // 异步
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -68,8 +87,8 @@ module.exports = {
|
|||
SpeedTester,
|
||||
initSpeedTest,
|
||||
getSpeedTester,
|
||||
getAllSpeedTester,
|
||||
registerNotify,
|
||||
// getAllSpeedTester,
|
||||
// registerNotify,
|
||||
reSpeedTest,
|
||||
action,
|
||||
}
|
||||
|
|
|
@ -26,3 +26,12 @@ assert.strictEqual(isEmpty(0), false)
|
|||
assert.strictEqual(isEmpty(-1), false)
|
||||
assert.strictEqual(isEmpty(''), false)
|
||||
assert.strictEqual(isEmpty('1'), false)
|
||||
|
||||
// test lodash.unionBy
|
||||
const list = [
|
||||
{ host: 1, port: 1, dns: 2 },
|
||||
{ host: 1, port: 1, dns: 3 },
|
||||
{ host: 1, port: 2, dns: 3 },
|
||||
{ host: 1, port: 2, dns: 3 },
|
||||
]
|
||||
console.info(lodash.unionBy(list, 'host', 'port'))
|
||||
|
|
Loading…
Reference in New Issue