fish2018/pansou · error
[ ] 读取搜索响应失败
Error message
[%s] 读取搜索响应失败: %w
What it means
Returned by searchImpl when io.ReadAll fails while reading the (limited) response body. The body is read through io.LimitReader(resp.Body, maxResponseSize+1) so oversized bodies are handled separately; this error indicates a genuine transport problem mid-read — the connection was closed, reset, or timed out before the body completed.
Solutions
- Retry the request once — this is typically transient
- Check whether a proxy/firewall between the host and the site truncates large responses
- Increase the timeout budget if body transfer is slow
- Log the wrapped *url.Error to confirm it is io.ErrUnexpectedEOF/connection-reset class
Defensive patterns
Strategy: retry
Try / catch
if err != nil && errors.Is(err, io.ErrUnexpectedEOF) {
// transient truncation: retry once with fresh request
} Prevention
- Retry truncated reads — usually transient
- Check intermediary proxies/firewalls for stream-terminating behavior
- Keep the LimitReader guard so huge/partial bodies fail fast
- Monitor for correlated failures with error 241 (same connection path)
When it happens
Trigger: The server or an intermediary closes/resets the TCP connection partway through the response body, a proxy terminates the stream, or the context deadline (25s) expires during body transfer.
Common situations: Flaky mobile/VPN connections, aggressive CDN idle timeouts cutting large HTML pages short, TLS interception proxies resetting streams.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/08ea8235079f4d86.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/haitunsou/haitunsou.go:105
defer cancel()
searchURL := fmt.Sprintf("%s/s/%s.html", strings.TrimRight(p.baseURL, "/"), url.PathEscape(keyword))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
if err != nil {
return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
}
setRequestHeaders(req, p.baseURL)
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("[%s] 搜索请求返回 HTTP %d", p.Name(), resp.StatusCode)
}
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))
if err != nil {
return nil, fmt.Errorf("[%s] 读取搜索响应失败: %w", p.Name(), err)
}
if len(body) > maxResponseSize {
return nil, fmt.Errorf("[%s] 搜索响应超过 %d 字节", p.Name(), maxResponseSize)
}
items, err := parseEmbeddedList(body)
if err != nil {
return nil, fmt.Errorf("[%s] 解析搜索结果失败: %w", p.Name(), err)
}
results := make([]model.SearchResult, 0, len(items))
seen := make(map[string]struct{}, len(items))
for _, item := range items {
result, ok := convertItem(item)
if !ok {
continue
}
key := result.Links[0].URL + "\x00" + result.Links[0].Password
if _, exists := seen[key]; exists {View on GitHub (pinned to beaa561337)