mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
322 lines
9.3 KiB
Go
322 lines
9.3 KiB
Go
package proxy
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/metacubex/mihomo/adapter"
|
||
"github.com/metacubex/mihomo/common/utils"
|
||
C "github.com/metacubex/mihomo/constant"
|
||
|
||
"ant-chrome/backend/internal/config"
|
||
"ant-chrome/backend/internal/logger"
|
||
)
|
||
|
||
// ─── Clash 标准测速 URL ───
|
||
// 使用 HTTP 与 Clash 客户端保持一致
|
||
|
||
const DefaultSpeedTestURL = "http://www.gstatic.com/generate_204"
|
||
|
||
// SpeedTestConfig 测速参数
|
||
type SpeedTestConfig struct {
|
||
Timeout time.Duration
|
||
TCPTimeout time.Duration
|
||
URLs []string
|
||
ExpectedStatus []int
|
||
}
|
||
|
||
var DefaultSpeedTestConfig = SpeedTestConfig{
|
||
Timeout: 3 * time.Second,
|
||
TCPTimeout: 3 * time.Second,
|
||
}
|
||
|
||
// ─── 对外入口 ───
|
||
|
||
// SpeedTest 按单个代理的内核决策执行轻量 HTTP 延迟测试。
|
||
func SpeedTest(
|
||
proxyId string,
|
||
proxies []config.BrowserProxy,
|
||
xrayMgr *XrayManager,
|
||
singboxMgr *SingBoxManager,
|
||
cfg *SpeedTestConfig,
|
||
) TestResult {
|
||
return SpeedTestWithConnector(proxyId, proxies, xrayMgr, singboxMgr, nil, config.BrowserConnectorXray, cfg)
|
||
}
|
||
|
||
// SpeedTestWithConnector 保留 connectorType 参数用于旧调用兼容。
|
||
// 实际测速内核由 ResolveProxyKernel 按单个代理决定。
|
||
func SpeedTestWithConnector(
|
||
proxyId string,
|
||
proxies []config.BrowserProxy,
|
||
xrayMgr *XrayManager,
|
||
singboxMgr *SingBoxManager,
|
||
clashMgr *ClashManager,
|
||
connectorType string,
|
||
cfg *SpeedTestConfig,
|
||
) TestResult {
|
||
connectorType = config.NormalizeBrowserConnectorType(connectorType)
|
||
return lightHTTPDelayTestWithConnector(proxyId, proxies, xrayMgr, singboxMgr, clashMgr, connectorType, cfg)
|
||
}
|
||
|
||
func lightHTTPDelayTestWithConnector(
|
||
proxyId string,
|
||
proxies []config.BrowserProxy,
|
||
xrayMgr *XrayManager,
|
||
singboxMgr *SingBoxManager,
|
||
clashMgr *ClashManager,
|
||
connectorType string,
|
||
cfg *SpeedTestConfig,
|
||
) TestResult {
|
||
log := logger.New("SpeedTest")
|
||
|
||
if cfg == nil {
|
||
c := DefaultSpeedTestConfig
|
||
cfg = &c
|
||
}
|
||
|
||
src := resolveProxyConfig("", proxies, proxyId)
|
||
if src == "" {
|
||
return TestResult{ProxyId: proxyId, Ok: false, Engine: connectorType, Error: "代理配置为空"}
|
||
}
|
||
|
||
if strings.ToLower(src) == "direct://" {
|
||
return TestResult{ProxyId: proxyId, Ok: true, LatencyMs: 0, Engine: "direct"}
|
||
}
|
||
|
||
testURLs := speedTestTargetURLs(cfg)
|
||
if len(testURLs) == 0 {
|
||
return TestResult{ProxyId: proxyId, Ok: false, Engine: connectorType, Error: "测速目标 URL 为空"}
|
||
}
|
||
engine := speedTestProbeEngine(src, proxies, proxyId, connectorType)
|
||
log.Info("开始代理测速",
|
||
logger.F("proxy_id", proxyId),
|
||
logger.F("engine", engine),
|
||
logger.F("timeout_ms", cfg.Timeout.Milliseconds()),
|
||
logger.F("tcp_timeout_ms", cfg.TCPTimeout.Milliseconds()),
|
||
logger.F("targets", strings.Join(testURLs, ",")),
|
||
)
|
||
|
||
client, err := buildSpeedTestHTTPClient(src, proxyId, proxies, xrayMgr, singboxMgr, clashMgr, connectorType, cfg)
|
||
if err != nil {
|
||
log.Warn("代理测速 HTTP 客户端创建失败",
|
||
logger.F("proxy_id", proxyId),
|
||
logger.F("error", err.Error()),
|
||
)
|
||
return TestResult{ProxyId: proxyId, Ok: false, Engine: engine, Error: err.Error()}
|
||
}
|
||
|
||
var lastErr error
|
||
var lastLatency int64
|
||
for _, testURL := range testURLs {
|
||
latency, statusCode, err := doSpeedTestRequest(client, testURL)
|
||
lastLatency = latency
|
||
if err != nil {
|
||
lastErr = err
|
||
log.Warn("代理测速请求失败",
|
||
logger.F("proxy_id", proxyId),
|
||
logger.F("engine", engine),
|
||
logger.F("url", testURL),
|
||
logger.F("latency_ms", latency),
|
||
logger.F("error", err.Error()),
|
||
)
|
||
continue
|
||
}
|
||
if speedTestStatusOK(statusCode, cfg) {
|
||
log.Info("代理测速成功",
|
||
logger.F("proxy_id", proxyId),
|
||
logger.F("engine", engine),
|
||
logger.F("url", testURL),
|
||
logger.F("status", statusCode),
|
||
logger.F("latency_ms", latency),
|
||
)
|
||
return TestResult{ProxyId: proxyId, Ok: true, LatencyMs: latency, Engine: engine}
|
||
}
|
||
lastErr = fmt.Errorf("HTTP %d", statusCode)
|
||
log.Warn("代理测速状态码不符合预期",
|
||
logger.F("proxy_id", proxyId),
|
||
logger.F("engine", engine),
|
||
logger.F("url", testURL),
|
||
logger.F("status", statusCode),
|
||
logger.F("latency_ms", latency),
|
||
)
|
||
}
|
||
|
||
if lastErr != nil {
|
||
log.Warn("代理测速失败",
|
||
logger.F("proxy_id", proxyId),
|
||
logger.F("engine", engine),
|
||
logger.F("latency_ms", lastLatency),
|
||
logger.F("error", lastErr.Error()),
|
||
)
|
||
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: lastLatency, Engine: engine, Error: lastErr.Error()}
|
||
}
|
||
log.Warn("代理测速失败", logger.F("proxy_id", proxyId), logger.F("engine", engine), logger.F("error", "测速失败"))
|
||
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: lastLatency, Engine: engine, Error: "测速失败"}
|
||
}
|
||
|
||
func buildSpeedTestHTTPClient(
|
||
src string,
|
||
proxyId string,
|
||
proxies []config.BrowserProxy,
|
||
xrayMgr *XrayManager,
|
||
singboxMgr *SingBoxManager,
|
||
clashMgr *ClashManager,
|
||
connectorType string,
|
||
cfg *SpeedTestConfig,
|
||
) (*http.Client, error) {
|
||
timeout := DefaultSpeedTestConfig.Timeout
|
||
prepareTimeout := DefaultSpeedTestConfig.TCPTimeout
|
||
if cfg != nil {
|
||
if cfg.Timeout > 0 {
|
||
timeout = cfg.Timeout
|
||
}
|
||
if cfg.TCPTimeout > 0 {
|
||
prepareTimeout = cfg.TCPTimeout
|
||
}
|
||
}
|
||
if prepareTimeout <= 0 {
|
||
prepareTimeout = timeout
|
||
}
|
||
|
||
type clientResult struct {
|
||
client *http.Client
|
||
err error
|
||
}
|
||
resultCh := make(chan clientResult, 1)
|
||
go func() {
|
||
client, err := buildProxyHTTPClient(src, proxyId, proxies, xrayMgr, singboxMgr, clashMgr, connectorType, timeout)
|
||
resultCh <- clientResult{client: client, err: err}
|
||
}()
|
||
|
||
timer := time.NewTimer(prepareTimeout)
|
||
defer timer.Stop()
|
||
select {
|
||
case result := <-resultCh:
|
||
return result.client, result.err
|
||
case <-timer.C:
|
||
return nil, fmt.Errorf("代理准备超时(%dms)", prepareTimeout.Milliseconds())
|
||
}
|
||
}
|
||
|
||
func primarySpeedTestURL(cfg *SpeedTestConfig) string {
|
||
if cfg != nil {
|
||
if urls := normalizeSpeedTestURLs(cfg.URLs); len(urls) > 0 {
|
||
return urls[0]
|
||
}
|
||
}
|
||
return strings.TrimSpace(DefaultSpeedTestURL)
|
||
}
|
||
|
||
func speedTestTargetURLs(cfg *SpeedTestConfig) []string {
|
||
if cfg != nil {
|
||
if urls := uniqueSpeedTestURLs(cfg.URLs); len(urls) > 0 {
|
||
return urls
|
||
}
|
||
}
|
||
return []string{DefaultSpeedTestURL}
|
||
}
|
||
|
||
func speedTestProbeEngine(src string, proxies []config.BrowserProxy, proxyId string, connectorType string) string {
|
||
resolution, err := ResolveProxyKernelForConnector(src, proxies, proxyId, connectorType)
|
||
if err != nil {
|
||
if resolution.Kernel != "" {
|
||
return resolution.Kernel
|
||
}
|
||
return config.NormalizeBrowserConnectorType(connectorType)
|
||
}
|
||
if resolution.Kernel == ProxyKernelNative {
|
||
return "native"
|
||
}
|
||
return resolution.Kernel
|
||
}
|
||
|
||
func doSpeedTestRequest(client *http.Client, testURL string) (int64, int, error) {
|
||
latency, statusCode, err := doSpeedTestRequestWithMethod(client, http.MethodHead, testURL)
|
||
if err != nil || statusCode != http.StatusMethodNotAllowed {
|
||
if err != nil {
|
||
return latency, statusCode, err
|
||
}
|
||
secondLatency, secondStatusCode, secondErr := doSpeedTestRequestWithMethod(client, http.MethodHead, testURL)
|
||
if secondErr == nil {
|
||
return secondLatency, secondStatusCode, nil
|
||
}
|
||
return latency, statusCode, nil
|
||
}
|
||
return doSpeedTestRequestWithMethod(client, http.MethodGet, testURL)
|
||
}
|
||
|
||
func doSpeedTestRequestWithMethod(client *http.Client, method string, testURL string) (int64, int, error) {
|
||
start := time.Now()
|
||
req, err := http.NewRequest(method, testURL, nil)
|
||
if err != nil {
|
||
return 0, 0, fmt.Errorf("测速请求创建失败: %w", err)
|
||
}
|
||
resp, err := client.Do(req)
|
||
latency := time.Since(start).Milliseconds()
|
||
if err != nil {
|
||
return latency, 0, err
|
||
}
|
||
_ = resp.Body.Close()
|
||
return latency, resp.StatusCode, nil
|
||
}
|
||
|
||
func speedTestStatusOK(statusCode int, cfg *SpeedTestConfig) bool {
|
||
if cfg != nil && len(cfg.ExpectedStatus) > 0 {
|
||
for _, expected := range cfg.ExpectedStatus {
|
||
if statusCode == expected {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
return isSpeedTestSuccessStatus(statusCode)
|
||
}
|
||
|
||
func mihomoURLTest(proxyId string, proxyInstance C.Proxy, testURL string, cfg *SpeedTestConfig) TestResult {
|
||
timeout := DefaultSpeedTestConfig.Timeout
|
||
if cfg != nil && cfg.Timeout > 0 {
|
||
timeout = cfg.Timeout
|
||
}
|
||
|
||
expectedStatus, err := speedTestExpectedStatus(cfg)
|
||
if err != nil {
|
||
return TestResult{ProxyId: proxyId, Ok: false, Engine: "mihomo", Error: err.Error()}
|
||
}
|
||
|
||
adapter.UnifiedDelay.Store(true)
|
||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||
defer cancel()
|
||
|
||
delay, err := proxyInstance.URLTest(ctx, testURL, expectedStatus)
|
||
latency := int64(delay)
|
||
if ctx.Err() != nil {
|
||
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: latency, Engine: "mihomo", Error: "测速超时"}
|
||
}
|
||
if err != nil || delay == 0 {
|
||
if err != nil {
|
||
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: latency, Engine: "mihomo", Error: err.Error()}
|
||
}
|
||
return TestResult{ProxyId: proxyId, Ok: false, LatencyMs: latency, Engine: "mihomo", Error: "mihomo 延迟测试无结果"}
|
||
}
|
||
|
||
return TestResult{ProxyId: proxyId, Ok: true, LatencyMs: latency, Engine: "mihomo"}
|
||
}
|
||
|
||
func speedTestExpectedStatus(cfg *SpeedTestConfig) (utils.IntRanges[uint16], error) {
|
||
if cfg == nil || len(cfg.ExpectedStatus) == 0 {
|
||
return nil, nil
|
||
}
|
||
items := make([]string, 0, len(cfg.ExpectedStatus))
|
||
for _, status := range cfg.ExpectedStatus {
|
||
if status <= 0 || status > 65535 {
|
||
return nil, fmt.Errorf("无效测速状态码: %d", status)
|
||
}
|
||
items = append(items, strconv.Itoa(status))
|
||
}
|
||
return utils.NewUnsignedRangesFromList[uint16](items)
|
||
}
|