测试代理是否可用
This commit is contained in:
148
core/check.go
148
core/check.go
@@ -9,116 +9,92 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
//for _, node := range global.Conf.Nodes {
|
||||
//for _, port := range node.Ports {
|
||||
//node.Port = port
|
||||
//success, message := core.CheckHttpsProxy(&node, global.Conf.TargetURL)
|
||||
//if success {
|
||||
//fmt.Printf("代理 %s 端口 %d 可用.\n", node.Addr, node.Port)
|
||||
//} else {
|
||||
//fmt.Printf("代理 %s 端口 %d 不可用: %s\n", node.Addr, node.Port, message)
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
// CheckHttpsProxy 检测代理是否可用的函数
|
||||
func CheckHttpsProxy(node *common.Node, targetURL string) (bool, string) {
|
||||
proxyURL, err := Node2ProxyURL(node)
|
||||
if err != nil {
|
||||
fmt.Printf("节点转换代理URL失败: %v\n", err)
|
||||
return false, err.Error()
|
||||
return false, fmt.Sprintf("节点转换代理URL失败: %v", err)
|
||||
}
|
||||
// 解析代理 URL
|
||||
|
||||
proxy, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
fmt.Printf("代理解析失败: %v\n", err)
|
||||
return false, err.Error()
|
||||
return false, fmt.Sprintf("代理解析失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建自定义 Transport,用于调试代理信息
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyURL(proxy), // 设置代理
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if global.Conf.Debug {
|
||||
fmt.Printf("网络拨号: %s, 地址: %s\n", network, addr)
|
||||
}
|
||||
conn, err := net.Dial(network, addr)
|
||||
if err != nil {
|
||||
fmt.Printf("网络拨号失败: %v\n", err)
|
||||
}
|
||||
return conn, err
|
||||
},
|
||||
}
|
||||
|
||||
// 创建 HTTP 客户端并使用自定义 Transport
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 30 * time.Second, // 设置超时时间
|
||||
}
|
||||
client := createHTTPClient(proxy)
|
||||
|
||||
if global.Conf.Debug {
|
||||
fmt.Printf("发送请求到: %s 使用代理: %s\n", targetURL, proxyURL)
|
||||
}
|
||||
// 发起 GET 请求
|
||||
|
||||
// 记录请求开始时间
|
||||
startTime := time.Now()
|
||||
|
||||
resp, err := client.Get(targetURL)
|
||||
if err != nil {
|
||||
fmt.Printf("通过代理发送请求失败 %s: %v\n", proxyURL, err)
|
||||
return false, err.Error()
|
||||
return false, fmt.Sprintf("通过代理发送请求失败 %s: %v", proxyURL, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if global.Conf.Debug {
|
||||
// 打印响应状态
|
||||
fmt.Printf("Response 状态码: %s\n", resp.Status)
|
||||
// 打印响应头
|
||||
fmt.Printf("Response 响应头: \n")
|
||||
for key, value := range resp.Header {
|
||||
fmt.Printf("\t%s: %s\n", key, value)
|
||||
}
|
||||
// 计算延迟(转换为毫秒)
|
||||
latencyMs := time.Since(startTime).Milliseconds()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, fmt.Sprintf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查网页标题
|
||||
title := extractTitle(string(body))
|
||||
if title == "" {
|
||||
return false, "未找到网页标题"
|
||||
}
|
||||
|
||||
if global.Conf.Debug {
|
||||
// 创建一个缓冲区,用于分块读取响应体
|
||||
buffer := make([]byte, 4096) // 4KB 缓冲区
|
||||
fmt.Printf("分块读取响应正文:\n")
|
||||
// 分块读取响应体
|
||||
for {
|
||||
n, err := resp.Body.Read(buffer)
|
||||
if err == io.EOF {
|
||||
fmt.Println("响应正文读取完毕.")
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("响应正文读取错误: %v\n", err)
|
||||
return false, err.Error()
|
||||
}
|
||||
// 打印每次读取的内容
|
||||
fmt.Printf("%s\n", string(buffer[:n])) // 不打印内容,只检测可用性
|
||||
}
|
||||
printResponseDetails(resp)
|
||||
fmt.Printf("网页标题: %s\n", title)
|
||||
}
|
||||
|
||||
// 检查是否发生了重定向
|
||||
if resp.StatusCode == 301 || resp.StatusCode == 302 {
|
||||
redirectURL := resp.Header.Get("Location")
|
||||
if global.Conf.Debug {
|
||||
fmt.Printf("重定向到: %s\n", redirectURL)
|
||||
}
|
||||
// 如果需要处理重定向,可以发起新的请求
|
||||
newResp, err := client.Get(redirectURL)
|
||||
if err != nil {
|
||||
fmt.Printf("重定向失败: %v\n", err)
|
||||
return false, err.Error()
|
||||
}
|
||||
defer newResp.Body.Close()
|
||||
|
||||
// 打印重定向后的响应状态
|
||||
if global.Conf.Debug {
|
||||
fmt.Printf("重定向响应状态: %s\n", newResp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
return true, "代理可用"
|
||||
return true, fmt.Sprintf("标题: %s, 延迟: %dms", title, latencyMs)
|
||||
}
|
||||
|
||||
// extractTitle 从HTML内容中提取标题
|
||||
func extractTitle(html string) string {
|
||||
// 匹配 <title> 标签内容,支持多行匹配
|
||||
titleRegex := regexp.MustCompile(`(?i)<title.*?>(.*?)</title>`)
|
||||
matches := titleRegex.FindStringSubmatch(html)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// createHTTPClient 创建一个带有代理设置的 HTTP 客户端
|
||||
func createHTTPClient(proxy *url.URL) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxy),
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if global.Conf.Debug {
|
||||
fmt.Printf("网络拨号: %s, 地址: %s\n", network, addr)
|
||||
}
|
||||
return net.Dial(network, addr)
|
||||
},
|
||||
},
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// printResponseDetails 打印响应的详细信息
|
||||
func printResponseDetails(resp *http.Response) {
|
||||
fmt.Printf("Response 状态码: %s\n", resp.Status)
|
||||
fmt.Printf("Response 响应头: \n")
|
||||
for key, value := range resp.Header {
|
||||
fmt.Printf("\t%s: %s\n", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
277
core/proxy_checker.go
Normal file
277
core/proxy_checker.go
Normal file
@@ -0,0 +1,277 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"httppp/common"
|
||||
"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++
|
||||
elapsed := time.Since(p.startTime)
|
||||
fmt.Printf("\r进度: %d/%d (%.1f%%) 已用时间: %v",
|
||||
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, 3)
|
||||
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 < maxRetries; i++ {
|
||||
success, message := CheckHttpsProxy(node, pc.targetURL)
|
||||
if success {
|
||||
return true, message
|
||||
}
|
||||
if !strings.Contains(message, "timeout") {
|
||||
return false, message
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
return false, fmt.Sprintf("重试%d次后仍然失败", maxRetries)
|
||||
}
|
||||
|
||||
// 添加一个新的结构体来保存节点和延迟信息
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 按延迟排序
|
||||
sort.Slice(nodesWithLatency, func(i, j int) bool {
|
||||
return nodesWithLatency[i].Latency < nodesWithLatency[j].Latency
|
||||
})
|
||||
|
||||
// 输出排序后的结果
|
||||
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 SaveResults(availableNodes []*common.Node, totalNodes int) {
|
||||
fmt.Printf("\n检测完成! 总计 %d 个节点:\n", totalNodes)
|
||||
fmt.Printf("✅ 可用节点: %d\n", len(availableNodes))
|
||||
fmt.Printf("❌ 不可用节点: %d\n", totalNodes-len(availableNodes))
|
||||
|
||||
if len(availableNodes) > 0 {
|
||||
filename := fmt.Sprintf("available_nodes_%s.txt", time.Now().Format("20060102_150405"))
|
||||
if err := common.SaveAvailableNodes(filename, availableNodes); err != nil {
|
||||
log.Printf("保存可用节点失败: %v", err)
|
||||
} else {
|
||||
fmt.Printf("可用节点已保存到: %s\n", filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 写入报告头部
|
||||
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))
|
||||
|
||||
// 写入可用节点
|
||||
fmt.Fprintf(w, "=== 可用节点列表 ===\n")
|
||||
for _, node := range availableNodes {
|
||||
fmt.Fprintf(w, "https://%s:%s@%s:%d\n",
|
||||
node.Username, node.Password, node.Addr, node.Port)
|
||||
}
|
||||
|
||||
// 写入不可用节点
|
||||
fmt.Fprintf(w, "\n=== 不可用节点列表 ===\n")
|
||||
for _, node := range unavailableNodes {
|
||||
fmt.Fprintf(w, "https://%s:%s@%s:%d\n",
|
||||
node.Username, node.Password, node.Addr, node.Port)
|
||||
}
|
||||
|
||||
// 写入报告时间
|
||||
fmt.Fprintf(w, "\n报告生成时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
|
||||
|
||||
w.Flush()
|
||||
//fmt.Printf("详细报告已保存到: %s\n", filename)
|
||||
}
|
||||
Reference in New Issue
Block a user