refactor(skills): reuse shared HTTP retry helper

This commit is contained in:
qs3c
2026-03-05 19:10:36 +08:00
parent 7a2fdc24dc
commit 536e9ac9de
+21 -95
View File
@@ -8,8 +8,6 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/sipeed/picoclaw/pkg/utils"
@@ -19,7 +17,6 @@ const (
defaultClawHubTimeout = 30 * time.Second
defaultMaxZipSize = 50 * 1024 * 1024 // 50 MB
defaultMaxResponseSize = 2 * 1024 * 1024 // 2 MB
defaultMaxRetries = 3
)
// ClawHubRegistry implements SkillRegistry for the ClawHub platform.
@@ -279,7 +276,12 @@ func (c *ClawHubRegistry) DownloadAndInstall(
// --- HTTP helper ---
func (c *ClawHubRegistry) doGet(ctx context.Context, urlStr string) ([]byte, error) {
resp, err := c.doGetWithRetry(ctx, urlStr, "application/json")
req, err := c.newGetRequest(ctx, urlStr, "application/json")
if err != nil {
return nil, err
}
resp, err := utils.DoRequestWithRetry(c.client, req)
if err != nil {
return nil, err
}
@@ -298,50 +300,25 @@ func (c *ClawHubRegistry) doGet(ctx context.Context, urlStr string) ([]byte, err
return body, nil
}
func (c *ClawHubRegistry) doGetWithRetry(ctx context.Context, urlStr, accept string) (*http.Response, error) {
var lastErr error
for attempt := 0; attempt < defaultMaxRetries; attempt++ {
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", accept)
if c.authToken != "" {
req.Header.Set("Authorization", "Bearer "+c.authToken)
}
resp, err := c.client.Do(req)
if err != nil {
lastErr = err
} else {
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return resp, nil
}
if !isRetryableStatus(resp.StatusCode) || attempt == defaultMaxRetries-1 {
return resp, nil
}
delay := retryDelay(resp.Header.Get("Retry-After"), attempt)
resp.Body.Close()
if err := sleepWithContext(ctx, delay); err != nil {
return nil, err
}
continue
}
if attempt == defaultMaxRetries-1 {
return nil, lastErr
}
if err := sleepWithContext(ctx, retryDelay("", attempt)); err != nil {
return nil, err
}
func (c *ClawHubRegistry) newGetRequest(ctx context.Context, urlStr, accept string) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return nil, err
}
return nil, lastErr
req.Header.Set("Accept", accept)
if c.authToken != "" {
req.Header.Set("Authorization", "Bearer "+c.authToken)
}
return req, nil
}
func (c *ClawHubRegistry) downloadToTempFileWithRetry(ctx context.Context, urlStr string) (string, error) {
resp, err := c.doGetWithRetry(ctx, urlStr, "application/zip")
req, err := c.newGetRequest(ctx, urlStr, "application/zip")
if err != nil {
return "", err
}
resp, err := utils.DoRequestWithRetry(c.client, req)
if err != nil {
return "", err
}
@@ -383,54 +360,3 @@ func (c *ClawHubRegistry) downloadToTempFileWithRetry(ctx context.Context, urlSt
return tmpPath, nil
}
func isRetryableStatus(statusCode int) bool {
return statusCode == http.StatusTooManyRequests || statusCode >= http.StatusInternalServerError
}
func retryDelay(retryAfter string, attempt int) time.Duration {
if d, ok := parseRetryAfter(retryAfter); ok {
return d
}
return time.Duration(attempt+1) * time.Second
}
func parseRetryAfter(headerValue string) (time.Duration, bool) {
headerValue = strings.TrimSpace(headerValue)
if headerValue == "" {
return 0, false
}
if sec, err := strconv.Atoi(headerValue); err == nil {
if sec < 0 {
sec = 0
}
return time.Duration(sec) * time.Second, true
}
if resetAt, err := http.ParseTime(headerValue); err == nil {
d := time.Until(resetAt)
if d < 0 {
d = 0
}
return d, true
}
return 0, false
}
func sleepWithContext(ctx context.Context, delay time.Duration) error {
if delay <= 0 {
return nil
}
timer := time.NewTimer(delay)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
return nil
}
}