mirror of
https://github.com/oneclickvirt/backtrace.git
synced 2025-08-28 02:00:39 +08:00
fix: 加入重试机制避免一次请求失败则全部失败
This commit is contained in:
parent
289739fff5
commit
7677003308
160
bgptools/pop.go
160
bgptools/pop.go
@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/imroc/req/v3"
|
||||
@ -41,14 +42,48 @@ type PoPResult struct {
|
||||
Result string
|
||||
}
|
||||
|
||||
// retryConfig 重试配置
|
||||
type retryConfig struct {
|
||||
maxRetries int
|
||||
timeouts []time.Duration
|
||||
}
|
||||
|
||||
// 默认重试配置:3次重试,超时时间分别为3s、4s、5s
|
||||
var defaultRetryConfig = retryConfig{
|
||||
maxRetries: 3,
|
||||
timeouts: []time.Duration{3 * time.Second, 4 * time.Second, 5 * time.Second},
|
||||
}
|
||||
|
||||
// executeWithRetry 执行带重试的HTTP请求
|
||||
func executeWithRetry(client *req.Client, url string, config retryConfig) (*req.Response, error) {
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < config.maxRetries; attempt++ {
|
||||
timeout := config.timeouts[attempt]
|
||||
resp, err := client.SetTimeout(timeout).R().
|
||||
Get(url)
|
||||
if err == nil && resp.StatusCode == 200 {
|
||||
return resp, nil
|
||||
}
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("attempt %d failed with timeout %v: %w", attempt+1, timeout, err)
|
||||
} else {
|
||||
lastErr = fmt.Errorf("attempt %d failed with HTTP status %d (timeout %v)", attempt+1, resp.StatusCode, timeout)
|
||||
}
|
||||
if attempt < config.maxRetries-1 {
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("all %d attempts failed, last error: %w", config.maxRetries, lastErr)
|
||||
}
|
||||
|
||||
func getISPAbbr(asn, name string) string {
|
||||
if abbr, ok := model.Tier1Global[asn]; ok {
|
||||
return abbr
|
||||
}
|
||||
if idx := strings.Index(name, " "); idx != -1 && idx > 18 {
|
||||
if idx := strings.Index(name, " "); idx != -1 && idx >= 18 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
return strings.TrimSpace(name)
|
||||
}
|
||||
|
||||
func getISPType(asn string, tier1 bool, direct bool) string {
|
||||
@ -74,43 +109,55 @@ func isValidIP(ip string) bool {
|
||||
return net.ParseIP(ip) != nil
|
||||
}
|
||||
|
||||
func getSVGPath(client *req.Client, ip string) (string, error) {
|
||||
func getSVGPath(ip string) (string, error) {
|
||||
if !isValidIP(ip) {
|
||||
return "", fmt.Errorf("invalid IP address: %s", ip)
|
||||
}
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < defaultRetryConfig.maxRetries; attempt++ {
|
||||
client := req.C().ImpersonateChrome()
|
||||
url := fmt.Sprintf("https://bgp.tools/prefix/%s#connectivity", ip)
|
||||
resp, err := client.R().Get(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch BGP info for IP %s: %w", ip, err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("HTTP error %d when fetching BGP info for IP %s", resp.StatusCode, ip)
|
||||
}
|
||||
resp, err := executeWithRetry(client, url, defaultRetryConfig)
|
||||
if err == nil {
|
||||
body := resp.String()
|
||||
re := regexp.MustCompile(`<img[^>]+id="pathimg"[^>]+src="([^"]+)"`)
|
||||
matches := re.FindStringSubmatch(body)
|
||||
if len(matches) < 2 {
|
||||
return "", fmt.Errorf("SVG path not found for IP %s", ip)
|
||||
}
|
||||
if len(matches) >= 2 {
|
||||
return matches[1], nil
|
||||
}
|
||||
lastErr = fmt.Errorf("SVG path not found for IP %s", ip)
|
||||
} else {
|
||||
lastErr = fmt.Errorf("failed to fetch BGP info for IP %s: %w", ip, err)
|
||||
}
|
||||
if attempt < defaultRetryConfig.maxRetries-1 {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("failed to get SVG path after %d retries: %w", defaultRetryConfig.maxRetries, lastErr)
|
||||
}
|
||||
|
||||
func downloadSVG(client *req.Client, svgPath string) (string, error) {
|
||||
func downloadSVG(svgPath string) (string, error) {
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < defaultRetryConfig.maxRetries; attempt++ {
|
||||
client := req.C().ImpersonateChrome()
|
||||
uuid := uuid.NewString()
|
||||
url := fmt.Sprintf("https://bgp.tools%s?%s&loggedin", svgPath, uuid)
|
||||
resp, err := client.R().Get(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to download SVG: %w", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("HTTP error %d when downloading SVG", resp.StatusCode)
|
||||
}
|
||||
resp, err := executeWithRetry(client, url, defaultRetryConfig)
|
||||
if err == nil {
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read SVG response body: %w", err)
|
||||
}
|
||||
if err == nil {
|
||||
return string(bodyBytes), nil
|
||||
}
|
||||
lastErr = fmt.Errorf("failed to read SVG response body: %w", err)
|
||||
} else {
|
||||
lastErr = fmt.Errorf("failed to download SVG: %w", err)
|
||||
}
|
||||
if attempt < defaultRetryConfig.maxRetries-1 {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("failed to download SVG after %d retries: %w", defaultRetryConfig.maxRetries, lastErr)
|
||||
}
|
||||
|
||||
func parseASAndEdges(svg string) ([]ASCard, []Arrow) {
|
||||
svg = html.UnescapeString(svg)
|
||||
@ -183,6 +230,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
|
||||
}
|
||||
}
|
||||
var upstreams []Upstream
|
||||
addedASNs := map[string]bool{}
|
||||
for _, n := range nodes {
|
||||
if !upstreamMap[n.ASN] {
|
||||
continue
|
||||
@ -196,6 +244,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
|
||||
Tier1: isTier1,
|
||||
Type: upstreamType,
|
||||
})
|
||||
addedASNs[n.ASN] = true
|
||||
}
|
||||
if len(upstreams) == 1 {
|
||||
currentASN := upstreams[0].ASN
|
||||
@ -214,14 +263,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
|
||||
nextASN = asn
|
||||
break
|
||||
}
|
||||
found := false
|
||||
for _, existing := range upstreams {
|
||||
if existing.ASN == nextASN {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
if addedASNs[nextASN] {
|
||||
break
|
||||
}
|
||||
var nextNode *ASCard
|
||||
@ -243,8 +285,56 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
|
||||
Tier1: isTier1,
|
||||
Type: upstreamType,
|
||||
})
|
||||
addedASNs[nextNode.ASN] = true
|
||||
currentASN = nextASN
|
||||
}
|
||||
} else if len(upstreams) > 1 {
|
||||
for _, directUpstream := range upstreams {
|
||||
currentASN := directUpstream.ASN
|
||||
for {
|
||||
nextUpstreams := map[string]bool{}
|
||||
for _, e := range edges {
|
||||
if e.From == currentASN {
|
||||
nextUpstreams[e.To] = true
|
||||
}
|
||||
}
|
||||
if len(nextUpstreams) != 1 {
|
||||
break
|
||||
}
|
||||
var nextASN string
|
||||
for asn := range nextUpstreams {
|
||||
nextASN = asn
|
||||
break
|
||||
}
|
||||
if addedASNs[nextASN] {
|
||||
break
|
||||
}
|
||||
var nextNode *ASCard
|
||||
for _, n := range nodes {
|
||||
if n.ASN == nextASN {
|
||||
nextNode = &n
|
||||
break
|
||||
}
|
||||
}
|
||||
if nextNode == nil {
|
||||
break
|
||||
}
|
||||
isTier1 := (nextNode.Fill == "white" && nextNode.Stroke == "#005ea5")
|
||||
if isTier1 {
|
||||
upstreamType := getISPType(nextNode.ASN, isTier1, false)
|
||||
upstreams = append(upstreams, Upstream{
|
||||
ASN: nextNode.ASN,
|
||||
Name: nextNode.Name,
|
||||
Direct: false,
|
||||
Tier1: isTier1,
|
||||
Type: upstreamType,
|
||||
})
|
||||
addedASNs[nextNode.ASN] = true
|
||||
break
|
||||
}
|
||||
currentASN = nextASN
|
||||
}
|
||||
}
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
@ -253,12 +343,12 @@ func GetPoPInfo(ip string) (*PoPResult, error) {
|
||||
if ip == "" {
|
||||
return nil, fmt.Errorf("IP address cannot be empty")
|
||||
}
|
||||
client := req.C().ImpersonateChrome()
|
||||
svgPath, err := getSVGPath(client, ip)
|
||||
|
||||
svgPath, err := getSVGPath(ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取SVG路径失败: %w", err)
|
||||
}
|
||||
svg, err := downloadSVG(client, svgPath)
|
||||
svg, err := downloadSVG(svgPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("下载SVG失败: %w", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user