Skip to content

Commit 7677003

Browse files
committed
fix: 加入重试机制避免一次请求失败则全部失败
1 parent 289739f commit 7677003

1 file changed

Lines changed: 132 additions & 42 deletions

File tree

bgptools/pop.go

Lines changed: 132 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net"
88
"regexp"
99
"strings"
10+
"time"
1011

1112
"github.com/google/uuid"
1213
"github.com/imroc/req/v3"
@@ -41,14 +42,48 @@ type PoPResult struct {
4142
Result string
4243
}
4344

45+
// retryConfig 重试配置
46+
type retryConfig struct {
47+
maxRetries int
48+
timeouts []time.Duration
49+
}
50+
51+
// 默认重试配置:3次重试,超时时间分别为3s、4s、5s
52+
var defaultRetryConfig = retryConfig{
53+
maxRetries: 3,
54+
timeouts: []time.Duration{3 * time.Second, 4 * time.Second, 5 * time.Second},
55+
}
56+
57+
// executeWithRetry 执行带重试的HTTP请求
58+
func executeWithRetry(client *req.Client, url string, config retryConfig) (*req.Response, error) {
59+
var lastErr error
60+
for attempt := 0; attempt < config.maxRetries; attempt++ {
61+
timeout := config.timeouts[attempt]
62+
resp, err := client.SetTimeout(timeout).R().
63+
Get(url)
64+
if err == nil && resp.StatusCode == 200 {
65+
return resp, nil
66+
}
67+
if err != nil {
68+
lastErr = fmt.Errorf("attempt %d failed with timeout %v: %w", attempt+1, timeout, err)
69+
} else {
70+
lastErr = fmt.Errorf("attempt %d failed with HTTP status %d (timeout %v)", attempt+1, resp.StatusCode, timeout)
71+
}
72+
if attempt < config.maxRetries-1 {
73+
time.Sleep(3 * time.Second)
74+
}
75+
}
76+
return nil, fmt.Errorf("all %d attempts failed, last error: %w", config.maxRetries, lastErr)
77+
}
78+
4479
func getISPAbbr(asn, name string) string {
4580
if abbr, ok := model.Tier1Global[asn]; ok {
4681
return abbr
4782
}
48-
if idx := strings.Index(name, " "); idx != -1 && idx > 18 {
83+
if idx := strings.Index(name, " "); idx != -1 && idx >= 18 {
4984
return name[:idx]
5085
}
51-
return name
86+
return strings.TrimSpace(name)
5287
}
5388

5489
func getISPType(asn string, tier1 bool, direct bool) string {
@@ -74,42 +109,54 @@ func isValidIP(ip string) bool {
74109
return net.ParseIP(ip) != nil
75110
}
76111

77-
func getSVGPath(client *req.Client, ip string) (string, error) {
112+
func getSVGPath(ip string) (string, error) {
78113
if !isValidIP(ip) {
79114
return "", fmt.Errorf("invalid IP address: %s", ip)
80115
}
81-
url := fmt.Sprintf("https://bgp.tools/prefix/%s#connectivity", ip)
82-
resp, err := client.R().Get(url)
83-
if err != nil {
84-
return "", fmt.Errorf("failed to fetch BGP info for IP %s: %w", ip, err)
85-
}
86-
if resp.StatusCode != 200 {
87-
return "", fmt.Errorf("HTTP error %d when fetching BGP info for IP %s", resp.StatusCode, ip)
88-
}
89-
body := resp.String()
90-
re := regexp.MustCompile(`<img[^>]+id="pathimg"[^>]+src="([^"]+)"`)
91-
matches := re.FindStringSubmatch(body)
92-
if len(matches) < 2 {
93-
return "", fmt.Errorf("SVG path not found for IP %s", ip)
116+
var lastErr error
117+
for attempt := 0; attempt < defaultRetryConfig.maxRetries; attempt++ {
118+
client := req.C().ImpersonateChrome()
119+
url := fmt.Sprintf("https://bgp.tools/prefix/%s#connectivity", ip)
120+
resp, err := executeWithRetry(client, url, defaultRetryConfig)
121+
if err == nil {
122+
body := resp.String()
123+
re := regexp.MustCompile(`<img[^>]+id="pathimg"[^>]+src="([^"]+)"`)
124+
matches := re.FindStringSubmatch(body)
125+
if len(matches) >= 2 {
126+
return matches[1], nil
127+
}
128+
lastErr = fmt.Errorf("SVG path not found for IP %s", ip)
129+
} else {
130+
lastErr = fmt.Errorf("failed to fetch BGP info for IP %s: %w", ip, err)
131+
}
132+
if attempt < defaultRetryConfig.maxRetries-1 {
133+
time.Sleep(1 * time.Second)
134+
}
94135
}
95-
return matches[1], nil
136+
return "", fmt.Errorf("failed to get SVG path after %d retries: %w", defaultRetryConfig.maxRetries, lastErr)
96137
}
97138

98-
func downloadSVG(client *req.Client, svgPath string) (string, error) {
99-
uuid := uuid.NewString()
100-
url := fmt.Sprintf("https://bgp.tools%s?%s&loggedin", svgPath, uuid)
101-
resp, err := client.R().Get(url)
102-
if err != nil {
103-
return "", fmt.Errorf("failed to download SVG: %w", err)
104-
}
105-
if resp.StatusCode != 200 {
106-
return "", fmt.Errorf("HTTP error %d when downloading SVG", resp.StatusCode)
107-
}
108-
bodyBytes, err := io.ReadAll(resp.Body)
109-
if err != nil {
110-
return "", fmt.Errorf("failed to read SVG response body: %w", err)
139+
func downloadSVG(svgPath string) (string, error) {
140+
var lastErr error
141+
for attempt := 0; attempt < defaultRetryConfig.maxRetries; attempt++ {
142+
client := req.C().ImpersonateChrome()
143+
uuid := uuid.NewString()
144+
url := fmt.Sprintf("https://bgp.tools%s?%s&loggedin", svgPath, uuid)
145+
resp, err := executeWithRetry(client, url, defaultRetryConfig)
146+
if err == nil {
147+
bodyBytes, err := io.ReadAll(resp.Body)
148+
if err == nil {
149+
return string(bodyBytes), nil
150+
}
151+
lastErr = fmt.Errorf("failed to read SVG response body: %w", err)
152+
} else {
153+
lastErr = fmt.Errorf("failed to download SVG: %w", err)
154+
}
155+
if attempt < defaultRetryConfig.maxRetries-1 {
156+
time.Sleep(1 * time.Second)
157+
}
111158
}
112-
return string(bodyBytes), nil
159+
return "", fmt.Errorf("failed to download SVG after %d retries: %w", defaultRetryConfig.maxRetries, lastErr)
113160
}
114161

115162
func parseASAndEdges(svg string) ([]ASCard, []Arrow) {
@@ -183,6 +230,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
183230
}
184231
}
185232
var upstreams []Upstream
233+
addedASNs := map[string]bool{}
186234
for _, n := range nodes {
187235
if !upstreamMap[n.ASN] {
188236
continue
@@ -196,6 +244,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
196244
Tier1: isTier1,
197245
Type: upstreamType,
198246
})
247+
addedASNs[n.ASN] = true
199248
}
200249
if len(upstreams) == 1 {
201250
currentASN := upstreams[0].ASN
@@ -214,14 +263,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
214263
nextASN = asn
215264
break
216265
}
217-
found := false
218-
for _, existing := range upstreams {
219-
if existing.ASN == nextASN {
220-
found = true
221-
break
222-
}
223-
}
224-
if found {
266+
if addedASNs[nextASN] {
225267
break
226268
}
227269
var nextNode *ASCard
@@ -243,8 +285,56 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
243285
Tier1: isTier1,
244286
Type: upstreamType,
245287
})
288+
addedASNs[nextNode.ASN] = true
246289
currentASN = nextASN
247290
}
291+
} else if len(upstreams) > 1 {
292+
for _, directUpstream := range upstreams {
293+
currentASN := directUpstream.ASN
294+
for {
295+
nextUpstreams := map[string]bool{}
296+
for _, e := range edges {
297+
if e.From == currentASN {
298+
nextUpstreams[e.To] = true
299+
}
300+
}
301+
if len(nextUpstreams) != 1 {
302+
break
303+
}
304+
var nextASN string
305+
for asn := range nextUpstreams {
306+
nextASN = asn
307+
break
308+
}
309+
if addedASNs[nextASN] {
310+
break
311+
}
312+
var nextNode *ASCard
313+
for _, n := range nodes {
314+
if n.ASN == nextASN {
315+
nextNode = &n
316+
break
317+
}
318+
}
319+
if nextNode == nil {
320+
break
321+
}
322+
isTier1 := (nextNode.Fill == "white" && nextNode.Stroke == "#005ea5")
323+
if isTier1 {
324+
upstreamType := getISPType(nextNode.ASN, isTier1, false)
325+
upstreams = append(upstreams, Upstream{
326+
ASN: nextNode.ASN,
327+
Name: nextNode.Name,
328+
Direct: false,
329+
Tier1: isTier1,
330+
Type: upstreamType,
331+
})
332+
addedASNs[nextNode.ASN] = true
333+
break
334+
}
335+
currentASN = nextASN
336+
}
337+
}
248338
}
249339
return upstreams
250340
}
@@ -253,12 +343,12 @@ func GetPoPInfo(ip string) (*PoPResult, error) {
253343
if ip == "" {
254344
return nil, fmt.Errorf("IP address cannot be empty")
255345
}
256-
client := req.C().ImpersonateChrome()
257-
svgPath, err := getSVGPath(client, ip)
346+
347+
svgPath, err := getSVGPath(ip)
258348
if err != nil {
259349
return nil, fmt.Errorf("获取SVG路径失败: %w", err)
260350
}
261-
svg, err := downloadSVG(client, svgPath)
351+
svg, err := downloadSVG(svgPath)
262352
if err != nil {
263353
return nil, fmt.Errorf("下载SVG失败: %w", err)
264354
}

0 commit comments

Comments
 (0)