refactor(skills): reuse shared HTTP retry helper
This commit is contained in:
parent
7a2fdc24dc
commit
536e9ac9de
1 changed files with 21 additions and 95 deletions
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/utils"
|
"github.com/sipeed/picoclaw/pkg/utils"
|
||||||
|
|
@ -19,7 +17,6 @@ const (
|
||||||
defaultClawHubTimeout = 30 * time.Second
|
defaultClawHubTimeout = 30 * time.Second
|
||||||
defaultMaxZipSize = 50 * 1024 * 1024 // 50 MB
|
defaultMaxZipSize = 50 * 1024 * 1024 // 50 MB
|
||||||
defaultMaxResponseSize = 2 * 1024 * 1024 // 2 MB
|
defaultMaxResponseSize = 2 * 1024 * 1024 // 2 MB
|
||||||
defaultMaxRetries = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClawHubRegistry implements SkillRegistry for the ClawHub platform.
|
// ClawHubRegistry implements SkillRegistry for the ClawHub platform.
|
||||||
|
|
@ -279,7 +276,12 @@ func (c *ClawHubRegistry) DownloadAndInstall(
|
||||||
// --- HTTP helper ---
|
// --- HTTP helper ---
|
||||||
|
|
||||||
func (c *ClawHubRegistry) doGet(ctx context.Context, urlStr string) ([]byte, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -298,50 +300,25 @@ func (c *ClawHubRegistry) doGet(ctx context.Context, urlStr string) ([]byte, err
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClawHubRegistry) doGetWithRetry(ctx context.Context, urlStr, accept string) (*http.Response, error) {
|
func (c *ClawHubRegistry) newGetRequest(ctx context.Context, urlStr, accept string) (*http.Request, error) {
|
||||||
var lastErr error
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
|
||||||
for attempt := 0; attempt < defaultMaxRetries; attempt++ {
|
if err != nil {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
|
return nil, err
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -383,54 +360,3 @@ func (c *ClawHubRegistry) downloadToTempFileWithRetry(ctx context.Context, urlSt
|
||||||
|
|
||||||
return tmpPath, nil
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue