fish2018/pansou · error
[ ] 读取响应体失败
Error message
[%s] 读取响应体失败: %w
What it means
executeSearch wraps the error from io.ReadAll when reading the search response body fails. The status was 200 but the body stream could not be fully consumed — typically a mid-stream connection reset/timeout or the 30s context expiring while the body was still streaming.
Solutions
- Retry the request — this is usually transient; doRequestWithRetry already wraps the send but the body read is outside it, so add read-phase retry in executeSearch
- Increase the 30s context timeout so large/slow bodies can finish streaming
- Log the partial bytes read and the wrapped error to distinguish reset vs deadline exceeded
- Check proxy/middlebox behavior if resets occur consistently on large responses
Example fix
// before
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应体失败: %w", p.Name(), err)
}
// after
err = context.Cause(ctx) // check if the deadline caused the read failure
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应体失败 (读取了 %d 字节): %w", p.Name(), len(respBody), err)
} Defensive patterns
Strategy: retry
Try / catch
respBody, err := io.ReadAll(resp.Body)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) {
return retryOnce()
}
return nil, fmt.Errorf("body read failed: %w", err)
} Prevention
- Include the body-read inside the retry wrapper, not only the request send
- Set the context timeout with headroom for body download, not just connection
- Avoid reading bodies over flaky proxies; prefer hosts with stable connectivity
When it happens
Trigger: io.ReadAll(resp.Body) returns err after a 200 status — connection dropped mid-body, context deadline (30s) exceeded during read, or chunked encoding truncated by a proxy.
Common situations: Slow/large responses exceeding the 30s deadline; flaky mobile/proxied networks; middleboxes resetting long-lived responses; server closing connections prematurely under load.
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/136bd28eef97798a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/xys/xys.go:274
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", BaseURL+"/")
req.Header.Set("Origin", BaseURL)
req.Header.Set("X-Requested-With", "XMLHttpRequest")
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[%s] 搜索请求HTTP状态错误: %d", p.Name(), resp.StatusCode)
}
// 读取响应体
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应体失败: %w", p.Name(), err)
}
// 解析JSON响应
var searchResp SearchResponse
if err := json.Unmarshal(respBody, &searchResp); err != nil {
return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
}
if searchResp.Code != 0 {
return nil, fmt.Errorf("[%s] 搜索API返回错误: %s", p.Name(), searchResp.Msg)
}
if p.debugMode {
log.Printf("[XYS] 搜索API响应成功,data长度: %d", len(searchResp.Data))
}
// 解析HTML内容
return p.parseSearchResults(searchResp.Data, keyword)View on GitHub (pinned to beaa561337)