mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
175 lines
5.2 KiB
Go
175 lines
5.2 KiB
Go
package proxy
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"ant-chrome/backend/internal/config"
|
|
)
|
|
|
|
// TestConnectivity 通过 TCP 握手测试代理服务器的可达性和延迟
|
|
// 直接对 server:port 建立 TCP 连接测量 RTT,无需启动外部进程
|
|
func TestConnectivity(proxyId string, proxyConfig string, proxies []config.BrowserProxy, _ interface{}) TestResult {
|
|
src := strings.TrimSpace(proxyConfig)
|
|
if proxyId != "" {
|
|
for _, item := range proxies {
|
|
if strings.EqualFold(item.ProxyId, proxyId) {
|
|
src = strings.TrimSpace(item.ProxyConfig)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if src == "" {
|
|
return TestResult{ProxyId: proxyId, Ok: false, Engine: "tcp", Error: "代理配置为空"}
|
|
}
|
|
|
|
endpoint, err := proxyEndpoint(src)
|
|
if err != nil {
|
|
return TestResult{ProxyId: proxyId, Ok: false, Engine: "tcp", Error: fmt.Sprintf("地址解析失败: %v", err)}
|
|
}
|
|
|
|
start := time.Now()
|
|
conn, err := net.DialTimeout("tcp", endpoint, 10*time.Second)
|
|
latency := time.Since(start).Milliseconds()
|
|
|
|
if err != nil {
|
|
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: latency, Engine: "tcp", Error: err.Error()}
|
|
}
|
|
conn.Close()
|
|
return TestResult{ProxyId: proxyId, Ok: true, LatencyMs: latency, Engine: "tcp"}
|
|
}
|
|
|
|
// TestRealConnectivity 通过代理链路发起真实 HTTP 请求测量端到端延迟。
|
|
// - DirectProxy (http/https/socks5):直接通过该代理发送请求
|
|
// - BridgeProxy (vmess/vless/Clash):调用 EnsureBridge 获取 socks5 地址后发送请求
|
|
// - SingBoxProxy (hysteria2/tuic):调用 SingBoxManager.EnsureBridge 后发送请求
|
|
func TestRealConnectivity(
|
|
proxyId string,
|
|
proxies []config.BrowserProxy,
|
|
xrayMgr *XrayManager,
|
|
) TestResult {
|
|
return TestRealConnectivityWithSingBox(proxyId, proxies, xrayMgr, nil)
|
|
}
|
|
|
|
// TestRealConnectivityWithSingBox 支持 sing-box 的真实连通性测试
|
|
func TestRealConnectivityWithSingBox(
|
|
proxyId string,
|
|
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 {
|
|
return TestRealConnectivityWithRuntimeConfig(proxyId, proxies, xrayMgr, singboxMgr, nil, config.BrowserConnectorXray, cfg)
|
|
}
|
|
|
|
func TestRealConnectivityWithRuntimeConfig(
|
|
proxyId string,
|
|
proxies []config.BrowserProxy,
|
|
xrayMgr *XrayManager,
|
|
singboxMgr *SingBoxManager,
|
|
clashMgr *ClashManager,
|
|
connectorType string,
|
|
cfg *SpeedTestConfig,
|
|
) TestResult {
|
|
src := resolveProxyConfig("", proxies, proxyId)
|
|
engine := speedTestProbeEngine(src, proxies, proxyId, connectorType)
|
|
if src == "" {
|
|
return TestResult{ProxyId: proxyId, Ok: false, Engine: engine, Error: "代理配置为空"}
|
|
}
|
|
|
|
targetURLs := defaultRealConnectivityTargets()
|
|
timeout := 15 * time.Second
|
|
if cfg != nil {
|
|
if len(cfg.URLs) > 0 {
|
|
configuredURLs := normalizeSpeedTestURLs(cfg.URLs)
|
|
if len(configuredURLs) > 0 {
|
|
targetURLs = append(configuredURLs, targetURLs...)
|
|
}
|
|
}
|
|
if cfg.Timeout > 0 {
|
|
timeout = cfg.Timeout
|
|
}
|
|
}
|
|
targetURLs = uniqueSpeedTestURLs(targetURLs)
|
|
if len(targetURLs) == 0 {
|
|
return TestResult{ProxyId: proxyId, Ok: false, Engine: engine, Error: "真实连通性测试目标 URL 为空"}
|
|
}
|
|
|
|
client, err := buildProxyHTTPClient(src, proxyId, proxies, xrayMgr, singboxMgr, clashMgr, connectorType, timeout)
|
|
if err != nil {
|
|
return TestResult{ProxyId: proxyId, Ok: false, Engine: engine, Error: err.Error()}
|
|
}
|
|
|
|
var lastErr error
|
|
var lastLatency int64
|
|
for _, targetURL := range targetURLs {
|
|
start := time.Now()
|
|
resp, err := client.Get(targetURL)
|
|
latency := time.Since(start).Milliseconds()
|
|
lastLatency = latency
|
|
if err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
_ = resp.Body.Close()
|
|
if isSpeedTestSuccessStatus(resp.StatusCode) {
|
|
return TestResult{ProxyId: proxyId, Ok: true, LatencyMs: latency, Engine: engine}
|
|
}
|
|
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
|
|
}
|
|
|
|
if lastErr != nil {
|
|
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: lastLatency, Engine: engine, Error: "真实访问失败: " + lastErr.Error()}
|
|
}
|
|
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: lastLatency, Engine: engine, Error: "真实连通性测试失败"}
|
|
}
|
|
|
|
func defaultRealConnectivityTargets() []string {
|
|
return []string{
|
|
DefaultSpeedTestURL,
|
|
"https://cp.cloudflare.com/generate_204",
|
|
"https://www.cloudflare.com/cdn-cgi/trace",
|
|
"http://www.msftconnecttest.com/connecttest.txt",
|
|
}
|
|
}
|
|
|
|
func normalizeSpeedTestURLs(urls []string) []string {
|
|
result := make([]string, 0, len(urls))
|
|
for _, item := range urls {
|
|
if item = strings.TrimSpace(item); item != "" {
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func uniqueSpeedTestURLs(urls []string) []string {
|
|
result := make([]string, 0, len(urls))
|
|
seen := map[string]struct{}{}
|
|
for _, item := range normalizeSpeedTestURLs(urls) {
|
|
key := strings.ToLower(item)
|
|
if _, ok := seen[key]; ok {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
result = append(result, item)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func isSpeedTestSuccessStatus(statusCode int) bool {
|
|
return statusCode == http.StatusNoContent || (statusCode >= 200 && statusCode < 300)
|
|
}
|