fish2018/pansou · error
[ ] 读取 API 响应失败
Error message
[%s] 读取 API 响应失败: %w
What it means
searchImpl reads the entire API response body with io.ReadAll; a failure here (premature connection close, read timeout, truncated chunked encoding) triggers the searchWeb fallback with this wrapped error. The response arrived but its body could not be fully consumed.
Solutions
- Inspect the wrapped io error (unexpected EOF vs context deadline)
- Increase DefaultTimeout if the deadline fires during body read
- Retry the request; add the body read inside doRequestWithRetry for retry coverage
- Prefer the searchWeb fallback results when API is flaky
Example fix
// before
body, err := io.ReadAll(resp.Body)
if err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 读取 API 响应失败: %w", p.Name(), err))
}
// after
body, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 读取 API 响应超时: %w", p.Name(), err))
}
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 读取 API 响应失败: %w", p.Name(), err))
} Defensive patterns
Strategy: retry
Validate before calling
// check response completeness signals
// (pre-request there is nothing to validate; ensure client timeouts are set)
client := &http.Client{Timeout: 30 * time.Second} Try / catch
results, err := plugin.Search(keyword)
if err != nil && strings.Contains(err.Error(), "读取 API 响应失败") {
if errors.Is(err, context.DeadlineExceeded) {
time.Sleep(time.Second)
results, err = plugin.Search(keyword) // one bounded retry
}
} Prevention
- Give body reads generous timeouts separate from connect timeouts
- Use io.LimitReader to avoid pathological huge/truncated bodies
- Retry idempotent GETs on mid-body failures
- Prefer stable networks/VPN endpoints when scraping geo-blocked sites
When it happens
Trigger: io.ReadAll(resp.Body) returns non-nil — connection reset mid-body, context deadline hit while streaming, invalid chunked transfer encoding.
Common situations: Flaky upstream connection, server/proxy cutting large responses short, DefaultTimeout expiring during body read, unstable network or VPN.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/2d1a56900f491b8b.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/feikuai/feikuai.go:139
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Referer", "https://feikuai.tv/")
// 发送请求(带重试)
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 搜索 API 请求失败: %w", p.Name(), err))
}
defer resp.Body.Close()
// 检查状态码
if resp.StatusCode != 200 {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 搜索 API 返回状态码: %d", p.Name(), resp.StatusCode))
}
// 读取并解析JSON响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] 读取 API 响应失败: %w", p.Name(), err))
}
var apiResp FeikuaiAPIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] API JSON 解析失败: %w", p.Name(), err))
}
// 检查API响应状态
if apiResp.Code != 0 {
return p.searchWeb(client, keyword, fmt.Errorf("[%s] API 返回错误: %s (code: %d)", p.Name(), apiResp.Msg, apiResp.Code))
}
// 解析搜索结果
var results []model.SearchResult
for _, item := range apiResp.Items {
// 每个item可能包含多个种子
for _, torrent := range item.Torrents {
result := p.parseTorrent(keyword, item, torrent)View on GitHub (pinned to beaa561337)