fix: measure proxy latency through browser bridge

This commit is contained in:
ant-black
2026-06-10 23:53:44 +08:00
parent eec33b305a
commit 3257de014e
8 changed files with 93 additions and 10 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ func (a *App) backupReloadAfterMutation() error {
a.speedScheduler = browser.NewProxySpeedScheduler(
a.browserMgr.ProxyDAO,
func(proxyID string) (bool, int64, string) {
r := proxy.SpeedTest(proxyID, a.config.Browser.Proxies, a.xrayMgr, a.singboxMgr, nil)
r := proxy.TestRealConnectivityWithConfig(proxyID, a.config.Browser.Proxies, a.xrayMgr, a.singboxMgr, nil)
return r.Ok, r.LatencyMs, r.Error
},
5*time.Minute,
+7 -4
View File
@@ -15,7 +15,7 @@ import (
// BrowserProxyTestSpeed 手动触发单个代理测速并持久化结果
func (a *App) BrowserProxyTestSpeed(proxyId string) ProxyTestResult {
proxies := a.getLatestProxies()
result := proxy.SpeedTest(proxyId, proxies, a.xrayMgr, a.singboxMgr, a.proxySpeedTestConfig())
result := proxy.TestRealConnectivityWithConfig(proxyId, proxies, a.xrayMgr, a.singboxMgr, a.proxySpeedTestConfig())
if a.browserMgr.ProxyDAO != nil {
testedAt := time.Now().Format(time.RFC3339)
_ = a.browserMgr.ProxyDAO.UpdateSpeedResult(proxyId, result.Ok, result.LatencyMs, testedAt)
@@ -23,13 +23,16 @@ func (a *App) BrowserProxyTestSpeed(proxyId string) ProxyTestResult {
return ProxyTestResult{ProxyId: result.ProxyId, Ok: result.Ok, LatencyMs: result.LatencyMs, Error: result.Error}
}
// BrowserProxyBatchTestSpeed 批量并发测速,concurrency 控制并发数(默认 20
// BrowserProxyBatchTestSpeed 批量并发测速,concurrency 控制并发数(默认 5,最高 5)。
func (a *App) BrowserProxyBatchTestSpeed(proxyIds []string, concurrency int) []ProxyTestResult {
if len(proxyIds) == 0 {
return []ProxyTestResult{}
}
if concurrency <= 0 {
concurrency = 20
concurrency = 5
}
if concurrency > 5 {
concurrency = 5
}
if concurrency > len(proxyIds) {
concurrency = len(proxyIds)
@@ -49,7 +52,7 @@ func (a *App) BrowserProxyBatchTestSpeed(proxyIds []string, concurrency int) []P
go func() {
defer wg.Done()
for job := range jobs {
result := proxy.SpeedTest(job.ProxyId, proxies, a.xrayMgr, a.singboxMgr, a.proxySpeedTestConfig())
result := proxy.TestRealConnectivityWithConfig(job.ProxyId, proxies, a.xrayMgr, a.singboxMgr, a.proxySpeedTestConfig())
if a.browserMgr.ProxyDAO != nil {
testedAt := time.Now().Format(time.RFC3339)
_ = a.browserMgr.ProxyDAO.UpdateSpeedResult(job.ProxyId, result.Ok, result.LatencyMs, testedAt)
+1 -1
View File
@@ -43,7 +43,7 @@ func (a *App) TestProxyConnectivity(proxyId string, proxyConfig string) ProxyTes
// 参考 Clash URLTest 策略:多 URL fallback + 复用桥接 + TCP ping 降级
func (a *App) TestProxyRealConnectivity(proxyId string) ProxyTestResult {
proxies := a.getLatestProxies()
result := proxy.SpeedTest(proxyId, proxies, a.xrayMgr, a.singboxMgr, nil)
result := proxy.TestRealConnectivityWithConfig(proxyId, proxies, a.xrayMgr, a.singboxMgr, nil)
return ProxyTestResult{ProxyId: result.ProxyId, Ok: result.Ok, LatencyMs: result.LatencyMs, Error: result.Error}
}
+1 -1
View File
@@ -202,7 +202,7 @@ func (a *App) startupInitSpeedScheduler() {
a.speedScheduler = browser.NewProxySpeedScheduler(
a.browserMgr.ProxyDAO,
func(proxyId string) (bool, int64, string) {
r := proxy.SpeedTest(proxyId, a.config.Browser.Proxies, a.xrayMgr, a.singboxMgr, nil)
r := proxy.TestRealConnectivityWithConfig(proxyId, a.config.Browser.Proxies, a.xrayMgr, a.singboxMgr, nil)
return r.Ok, r.LatencyMs, r.Error
},
5*time.Minute,
@@ -0,0 +1,62 @@
package proxy
import (
"encoding/json"
"os"
"testing"
"ant-chrome/backend/internal/config"
)
func TestXrayRuntimeConfigUsesWarningLogLevel(t *testing.T) {
cfg := config.DefaultConfig()
cfg.Browser.UserDataRoot = t.TempDir()
manager := &XrayManager{Config: cfg, AppRoot: t.TempDir()}
cfgPath, err := manager.buildRuntimeConfigWithRoute(
"log-level-test",
[]interface{}{map[string]interface{}{"protocol": "freedom", "tag": "proxy-out"}},
[]interface{}{},
19092,
"",
)
if err != nil {
t.Fatalf("buildRuntimeConfigWithRoute returned error: %v", err)
}
runtimeConfig := readRuntimeConfigMap(t, cfgPath)
logConfig := runtimeConfig["log"].(map[string]interface{})
if got := logConfig["loglevel"]; got != "warning" {
t.Fatalf("xray loglevel = %v, want warning", got)
}
}
func TestSingBoxRuntimeConfigUsesWarnLogLevel(t *testing.T) {
cfg := config.DefaultConfig()
cfg.Browser.UserDataRoot = t.TempDir()
manager := &SingBoxManager{Config: cfg, AppRoot: t.TempDir()}
cfgPath, err := manager.buildConfig("log-level-test", map[string]interface{}{"type": "direct", "tag": "proxy-out"}, 19093)
if err != nil {
t.Fatalf("buildConfig returned error: %v", err)
}
runtimeConfig := readRuntimeConfigMap(t, cfgPath)
logConfig := runtimeConfig["log"].(map[string]interface{})
if got := logConfig["level"]; got != "warn" {
t.Fatalf("sing-box log level = %v, want warn", got)
}
}
func readRuntimeConfigMap(t *testing.T, path string) map[string]interface{} {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read runtime config failed: %v", err)
}
var runtimeConfig map[string]interface{}
if err := json.Unmarshal(data, &runtimeConfig); err != nil {
t.Fatalf("unmarshal runtime config failed: %v", err)
}
return runtimeConfig
}
@@ -87,7 +87,7 @@ func (m *SingBoxManager) buildConfig(key string, outbound map[string]interface{}
cfg := map[string]interface{}{
"log": map[string]interface{}{
"level": "info",
"level": "warn",
"output": filepath.Join(baseDir, "singbox.log"),
"timestamp": true,
},
+19 -1
View File
@@ -60,6 +60,16 @@ func TestRealConnectivityWithSingBox(
proxies []config.BrowserProxy,
xrayMgr *XrayManager,
singboxMgr *SingBoxManager,
) TestResult {
return TestRealConnectivityWithConfig(proxyId, proxies, xrayMgr, singboxMgr, nil)
}
func TestRealConnectivityWithConfig(
proxyId string,
proxies []config.BrowserProxy,
xrayMgr *XrayManager,
singboxMgr *SingBoxManager,
cfg *SpeedTestConfig,
) TestResult {
src := resolveProxyConfig("", proxies, proxyId)
if src == "" {
@@ -67,10 +77,18 @@ func TestRealConnectivityWithSingBox(
}
targetURL := strings.TrimSpace(DefaultSpeedTestURL)
timeout := 15 * time.Second
if cfg != nil {
if len(cfg.URLs) > 0 && strings.TrimSpace(cfg.URLs[0]) != "" {
targetURL = strings.TrimSpace(cfg.URLs[0])
}
if cfg.Timeout > 0 {
timeout = cfg.Timeout
}
}
if targetURL == "" {
return TestResult{ProxyId: proxyId, Ok: false, Error: "真实连通性测试目标 URL 为空"}
}
const timeout = 15 * time.Second
client, err := buildProxyHTTPClient(src, proxyId, proxies, xrayMgr, singboxMgr, timeout)
if err != nil {
@@ -32,7 +32,7 @@ func (m *XrayManager) buildRuntimeConfigWithRoute(key string, outbounds []interf
cfgPath := filepath.Join(baseDir, "xray-config.json")
cfg := map[string]interface{}{
"log": map[string]interface{}{
"loglevel": "info",
"loglevel": "warning",
"error": filepath.Join(baseDir, "xray-error.log"),
},
"inbounds": []interface{}{