fish2018/pansou · error
[ ] 读取响应失败
Error message
[%s] 读取响应失败: %w
What it means
After a successful HTTP response, searchImpl reads the entire body with io.ReadAll. If reading fails (connection reset mid-response, context timeout during body transfer, truncated response), it wraps the error as '读取响应失败' (failed to read response).
Solutions
- Retry the request (doRequestWithRetry may already have succeeded but the body read is outside it — move body reading inside the retry)
- Increase the context timeout so the body read is not cut off
- Disable keep-alive or set DisableKeepAlives to avoid stale reused connections
- Check whether a proxy or LB is truncating responses
Example fix
// before
resp, err := p.doRequestWithRetry(req, client)
if err != nil { return nil, ... }
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil { return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err) }
// after
body, err := p.readBodyWithRetry(req, client) // body read inside retry loop
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
} Defensive patterns
Strategy: retry
Try / catch
body, err := io.ReadAll(resp.Body)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) {
return retrySearch(query) // transient truncation — retry
}
return nil, err
} Prevention
- Keep the request context alive long enough to read the full body
- Disable keep-alive if stale connections cause resets
- Read the body inside the retry loop so truncated reads are retried
- Set reasonable client timeouts (dial/response/header)
When it happens
Trigger: The server closes the connection before the body completes; the request context times out while streaming the body; a proxy drops the connection; extremely large/aborted responses.
Common situations: Unstable network to an overseas mirror; server-side idle timeouts cutting long responses; rate limiting that closes connections; keep-alive connections reused after the server timed them out.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/f09805e284d979e8.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/wanou/wanou.go:149
// 设置请求头
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Referer", "https://woog.nxog.eu.org/")
req.Header.Set("Cache-Control", "no-cache")
// 发送请求
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
// 解析JSON响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
}
var apiResponse WanouAPIResponse
if err := json.Unmarshal(body, &apiResponse); err != nil {
return nil, fmt.Errorf("[%s] 解析JSON响应失败: %w", p.Name(), err)
}
// 检查API响应状态
if apiResponse.Code != 1 {
return nil, fmt.Errorf("[%s] API返回错误: %s", p.Name(), apiResponse.Msg)
}
// 解析搜索结果
var results []model.SearchResult
for _, item := range apiResponse.List {
if result := p.parseAPIItem(item); result.Title != "" {
results = append(results, result)
}View on GitHub (pinned to beaa561337)