fish2018/pansou · error
HTTP
Error message
HTTP %d
What it means
Inside doLingjiGET's retry loop, when a non-nil error occurred and lastErr ended up nil, the code fabricates a fallback error "HTTP <status>" from resp.StatusCode. This is a defensive branch for the case where the response indicated failure but no underlying error object was captured — it surfaces the numeric HTTP status as the failure reason.
Solutions
- Check the status number in the message: 403→add browser headers, 429→slow down/backoff, 404→update API path, 5xx→retry later
- Increase lingjiMaxRetries so transient 5xx/429 are retried with the existing exponential backoff
- Send browser-like User-Agent/Referer headers to avoid 403 from WAF
- Verify lingjiAPIBase URL is current if seeing 404
- Handle the response status explicitly (return on expected statuses) instead of relying on the nil-err fallback
Example fix
// before
lastErr = err
if lastErr == nil {
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
}
// after
if err != nil {
lastErr = err
} else {
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
}
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
cancel()
continue // retryable
}
return nil, lastErr // non-retryable, fail fast Defensive patterns
Strategy: retry
Validate before calling
// Pre-check expected status handling before the retry loop
statusOK := func(code int) bool { return code >= 200 && code < 300 }
if resp != nil && !statusOK(resp.StatusCode) {
return classifyAndMaybeRetry(resp.StatusCode) // 429/5xx retry, 4xx fail fast
} Try / catch
body, err := doLingjiGET(client, apiURL, timeout)
if err != nil {
var httpErr interface{ HTTPStatus() int }
if errors.As(err, &httpErr) && httpErr.HTTPStatus() == http.StatusTooManyRequests {
time.Sleep(longerBackoff)
body, err = doLingjiGET(client, apiURL, timeout)
}
if err != nil {
return nil, err
}
} Prevention
- Classify status codes: retry 429/5xx, fail fast on 4xx
- Send browser-like headers to avoid 403s
- Keep lingjiMaxRetries and the exponential backoff base tuned for burst traffic
- Log the final status after all retries for monitoring
When it happens
Trigger: An attempt received an HTTP response whose status was treated as failure (non-2xx/expected), but err from the request was nil, so the loop synthesizes fmt.Errorf("HTTP %d", resp.StatusCode) as lastErr — e.g. 403, 404, 429, 502 from the API.
Common situations: Upstream returns 429 during bursts of search/detail calls; API path changed yielding 404; WAF returns 403 to non-browser clients; gateway 502/504 during API downtime. The error then surfaces wrapped by 搜索请求失败/详情请求失败 after all retries.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/1117af151c9e6a62.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/lingjisp/lingjisp.go:276
if n > 0 {
data = append(data, buffer[:n]...)
}
if readErr != nil {
if strings.Contains(readErr.Error(), "EOF") {
cancel()
return data, nil
}
lastErr = readErr
break
}
}
} else {
if resp != nil {
resp.Body.Close()
}
lastErr = err
if lastErr == nil {
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
}
}
cancel()
if attempt < lingjiMaxRetries-1 {
time.Sleep(200 * time.Millisecond * time.Duration(1<<attempt))
}
}
return nil, fmt.Errorf("重试 %d 次后失败: %w", lingjiMaxRetries, lastErr)
}
func dedupeLingjiItems(items []lingjiVideoItem) []lingjiVideoItem {
seen := make(map[int]struct{})
results := make([]lingjiVideoItem, 0, len(items))
for _, item := range items {
id := chooseLingjiID(item)View on GitHub (pinned to beaa561337)