273 lines
6.9 KiB
Go
273 lines
6.9 KiB
Go
package core
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"httppp/common"
|
|
"httppp/global"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// simplifyErrorMessage 简化错误消息
|
|
func simplifyErrorMessage(msg string) string {
|
|
// 常见错误类型
|
|
errorPatterns := map[string]string{
|
|
"no such host": "域名解析失败",
|
|
"connection refused": "连接被拒绝",
|
|
"connection reset": "连接被重置",
|
|
"timeout": "连接超时",
|
|
"EOF": "连接中断",
|
|
"i/o timeout": "连接超时",
|
|
"connection timed out": "连接超时",
|
|
"network is unreachable": "网络不可达",
|
|
"certificate": "证书错误",
|
|
"tls": "TLS错误",
|
|
"read: connection": "连接断开",
|
|
}
|
|
|
|
msg = strings.ToLower(msg)
|
|
for pattern, simpleMsg := range errorPatterns {
|
|
if strings.Contains(msg, pattern) {
|
|
return simpleMsg
|
|
}
|
|
}
|
|
|
|
return "连接失败"
|
|
}
|
|
|
|
type ProxyResult struct {
|
|
Node *common.Node
|
|
Success bool
|
|
Message string
|
|
}
|
|
|
|
type ProxyChecker struct {
|
|
workers int
|
|
nodes []*common.Node
|
|
targetURL string
|
|
progress *Progress
|
|
config common.ProxyCheck
|
|
}
|
|
|
|
type Progress struct {
|
|
total int
|
|
current int
|
|
mu sync.Mutex
|
|
startTime time.Time
|
|
}
|
|
|
|
func (p *Progress) increment() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.current++
|
|
if global.Conf.Output.ShowProgress { // 根据配置决定是否显示进度
|
|
elapsed := time.Since(p.startTime)
|
|
fmt.Printf("\r进度: %d/%d (%.1f%%) 已用时间: %v\n",
|
|
p.current, p.total,
|
|
float64(p.current)/float64(p.total)*100,
|
|
elapsed.Round(time.Second))
|
|
}
|
|
}
|
|
|
|
func NewProxyChecker(config common.ProxyCheck, nodes []*common.Node, targetURL string) *ProxyChecker {
|
|
return &ProxyChecker{
|
|
workers: config.Workers,
|
|
nodes: nodes,
|
|
targetURL: targetURL,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
func (pc *ProxyChecker) Check() ([]*common.Node, []*common.Node) {
|
|
pc.progress = &Progress{
|
|
total: len(pc.nodes),
|
|
startTime: time.Now(),
|
|
}
|
|
results := make(chan ProxyResult, len(pc.nodes))
|
|
var wg sync.WaitGroup
|
|
|
|
// 创建工作协程池
|
|
nodeChan := make(chan *common.Node, pc.workers)
|
|
|
|
// 启动工作协程
|
|
for i := 0; i < pc.workers; i++ {
|
|
wg.Add(1)
|
|
go pc.worker(&wg, nodeChan, results)
|
|
}
|
|
|
|
// 发送任务到工作协程
|
|
go func() {
|
|
for _, node := range pc.nodes {
|
|
nodeChan <- node
|
|
}
|
|
close(nodeChan)
|
|
}()
|
|
|
|
// 等待所有检测完成
|
|
go func() {
|
|
wg.Wait()
|
|
close(results)
|
|
}()
|
|
|
|
return pc.processResults(results)
|
|
}
|
|
|
|
func (pc *ProxyChecker) worker(wg *sync.WaitGroup, nodes chan *common.Node, results chan<- ProxyResult) {
|
|
defer wg.Done()
|
|
for node := range nodes {
|
|
success, message := pc.checkWithRetry(node, pc.config.MaxRetries)
|
|
results <- ProxyResult{
|
|
Node: node,
|
|
Success: success,
|
|
Message: message,
|
|
}
|
|
pc.progress.increment()
|
|
}
|
|
}
|
|
|
|
func (pc *ProxyChecker) checkWithRetry(node *common.Node, maxRetries int) (bool, string) {
|
|
// 使用配置中的重试次数
|
|
for i := 0; i < pc.config.MaxRetries; i++ {
|
|
success, message := CheckHttpsProxy(node, pc.targetURL)
|
|
if success {
|
|
return true, message
|
|
}
|
|
if !strings.Contains(message, "timeout") {
|
|
return false, message
|
|
}
|
|
// 使用配置中的重试延迟
|
|
time.Sleep(pc.config.RetryDelay)
|
|
}
|
|
return false, fmt.Sprintf("重试%d次后仍然失败", pc.config.MaxRetries)
|
|
}
|
|
|
|
// NodeWithLatency 添加一个新的结构体来保存节点和延迟信息
|
|
type NodeWithLatency struct {
|
|
Node *common.Node
|
|
Latency int64 // 延迟(毫秒)
|
|
Message string // 保存原始消息
|
|
}
|
|
|
|
func (pc *ProxyChecker) processResults(results <-chan ProxyResult) ([]*common.Node, []*common.Node) {
|
|
var availableNodes []*common.Node
|
|
var unavailableNodes []*common.Node
|
|
var nodesWithLatency []NodeWithLatency
|
|
|
|
// 收集结果
|
|
for result := range results {
|
|
if result.Success {
|
|
latency := extractLatency(result.Message)
|
|
nodesWithLatency = append(nodesWithLatency, NodeWithLatency{
|
|
Node: result.Node,
|
|
Latency: latency,
|
|
Message: result.Message,
|
|
})
|
|
} else {
|
|
if global.Conf.Output.ShowTitle { // 根据配置决定是否显示错误信息
|
|
if strings.Contains(result.Message, "标题") {
|
|
fmt.Printf("❌ %s:%d [%s]\n",
|
|
result.Node.Addr,
|
|
result.Node.Port,
|
|
result.Message)
|
|
} else {
|
|
fmt.Printf("❌ %s:%d (%s)\n",
|
|
result.Node.Addr,
|
|
result.Node.Port,
|
|
simplifyErrorMessage(result.Message))
|
|
}
|
|
}
|
|
unavailableNodes = append(unavailableNodes, result.Node)
|
|
}
|
|
}
|
|
|
|
if pc.config.SortByLatency {
|
|
sort.Slice(nodesWithLatency, func(i, j int) bool {
|
|
return nodesWithLatency[i].Latency < nodesWithLatency[j].Latency
|
|
})
|
|
}
|
|
|
|
// 输出排序后的结果
|
|
if global.Conf.Output.ShowTitle {
|
|
for _, nwl := range nodesWithLatency {
|
|
fmt.Printf("✅ %s:%d [%s]\n",
|
|
nwl.Node.Addr,
|
|
nwl.Node.Port,
|
|
extractTitleAndLatency(nwl.Message))
|
|
availableNodes = append(availableNodes, nwl.Node)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
return availableNodes, unavailableNodes
|
|
}
|
|
|
|
// 从消息中提取延迟时间
|
|
func extractLatency(message string) int64 {
|
|
re := regexp.MustCompile(`延迟: (\d+)ms`)
|
|
matches := re.FindStringSubmatch(message)
|
|
if len(matches) > 1 {
|
|
if latency, err := strconv.ParseInt(matches[1], 10, 64); err == nil {
|
|
return latency
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// 从消息中提取标题和延迟信息
|
|
func extractTitleAndLatency(message string) string {
|
|
re := regexp.MustCompile(`标题: (.*?), 延迟: (\d+)ms`)
|
|
matches := re.FindStringSubmatch(message)
|
|
if len(matches) > 2 {
|
|
return matches[0] // 返回完整的匹配结果
|
|
}
|
|
return message
|
|
}
|
|
|
|
func SaveDetailedReport(filename string, availableNodes []*common.Node, unavailableNodes []*common.Node, duration time.Duration) {
|
|
file, err := os.Create(filename)
|
|
if err != nil {
|
|
log.Printf("创建报告文件失败: %v", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
w := bufio.NewWriter(file)
|
|
defer w.Flush()
|
|
|
|
// 写入报告内容
|
|
writeReportContent(w, availableNodes, unavailableNodes, duration)
|
|
}
|
|
|
|
func writeReportContent(w *bufio.Writer, availableNodes, unavailableNodes []*common.Node, duration time.Duration) {
|
|
// 写入报告头部
|
|
fmt.Fprintf(w, "代理测试报告\n")
|
|
fmt.Fprintf(w, "测试时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
|
|
fmt.Fprintf(w, "测试用时: %v\n", duration)
|
|
fmt.Fprintf(w, "总节点数: %d\n", len(availableNodes)+len(unavailableNodes))
|
|
fmt.Fprintf(w, "可用节点: %d\n", len(availableNodes))
|
|
fmt.Fprintf(w, "不可用节点: %d\n\n", len(unavailableNodes))
|
|
|
|
// 写入节点列表
|
|
writeNodeList(w, "可用节点列表", availableNodes)
|
|
writeNodeList(w, "不可用节点列表", unavailableNodes)
|
|
|
|
// 写入报告时间
|
|
fmt.Fprintf(w, "\n报告生成时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
|
|
}
|
|
|
|
func writeNodeList(w *bufio.Writer, title string, nodes []*common.Node) {
|
|
fmt.Fprintf(w, "=== %s ===\n", title)
|
|
for _, node := range nodes {
|
|
fmt.Fprintf(w, "https://%s:%s@%s:%d\n",
|
|
node.Username, node.Password, node.Addr, node.Port)
|
|
}
|
|
fmt.Fprintln(w)
|
|
}
|