fish2018/pansou · error

[ ] 读取响应失败

Error message

[%s] 读取响应失败: %w

What it means

Reading the ASH response body with io.ReadAll on a 2MB LimitReader failed. The plugin wraps the I/O error as '读取响应失败'. This means the connection broke mid-body (EOF, reset, unexpected EOF) rather than the content being too large — the LimitReader caps size, not correctness.

Solutions

  1. Retry the request (the existing retry loop does not cover body reading); a single re-issue usually succeeds.
  2. Unwrap the error to distinguish context deadline exceeded (raise the 15s timeout) from connection reset (network/site issue).
  3. Read incrementally with buffered reads so partial data and the exact failure point can be logged.
  4. Check for proxies/middleboxes truncating responses and test from a different network.

Example fix

// before
body, err := io.ReadAll(limitReader)
if err != nil {
    return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
}
// after
body, err := io.ReadAll(limitReader)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return nil, fmt.Errorf("[%s] 读取响应超时: %w", p.Name(), err)
    }
    return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
}
Defensive patterns

Strategy: retry

Validate before calling

// re-issue the request if body read fails once
if err != nil && errors.Is(err, io.ErrUnexpectedEOF) { retry once }

Try / catch

body, err := io.ReadAll(limitReader)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) { /* raise timeout, retry */ }
    else { /* retry request; log truncated read */ }
}

Prevention

When it happens

Trigger: io.ReadAll(limitReader) on resp.Body returns a non-nil error: connection reset by peer, unexpected EOF, context deadline exceeded while streaming — ash.go:107.

Common situations: Server closes the connection early under load or anti-bot filtering; flaky mobile/proxied network; TLS interception cutting the stream; very slow site exceeding the 15s context timeout mid-read.

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/522f1982dab881b1. Report an issue: GitHub.

Appendix: source

Thrown at plugin/ash/ash.go:107

	
	// 发送请求(优化重试)
	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] 请求返回状态码: %d", p.Name(), resp.StatusCode)
	}
	
	// 读取响应(使用有限制的读取,避免读取过大内容)
	// ASH页面通常不会太大,限制在2MB以内
	limitReader := io.LimitReader(resp.Body, 2*1024*1024)
	body, err := io.ReadAll(limitReader)
	if err != nil {
		return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
	}
	
	// 从HTML中提取JSON数据(直接传递字节,避免字符串转换)
	results, err := p.extractResultsFromBytes(body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 提取搜索结果失败: %w", p.Name(), err)
	}
	
	// 关键词过滤
	filtered := plugin.FilterResultsByKeyword(results, keyword)
	
	return filtered, nil
}

// extractResultsFromBytes 从字节数组中提取搜索结果(优化版本,避免字符串转换)
func (p *AshPlugin) extractResultsFromBytes(data []byte) ([]model.SearchResult, error) {
	// 直接在字节数组中查找JSON数据(避免转换为字符串)
	html := string(data) // 只转换一次

View on GitHub (pinned to beaa561337)