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

  1. Retry the request once — this is typically transient
  2. Check whether a proxy/firewall between the host and the site truncates large responses
  3. Increase the timeout budget if body transfer is slow
  4. 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

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)