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

  1. 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
  2. Increase the 30s context timeout so large/slow bodies can finish streaming
  3. Log the partial bytes read and the wrapped error to distinguish reset vs deadline exceeded
  4. 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

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)