fish2018/pansou · error

[ ] 读取响应体失败

Error message

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

What it means

searchImpl reads the entire response body with io.ReadAll after a 200 status. This error wraps a read failure, which happens when the connection is broken mid-body: server closes the connection prematurely, keep-alive idle connection was reused after it died, TLS error mid-stream, or a proxy truncates the response.

Solutions

  1. Log the wrapped %w error to confirm premature-close vs TLS issue
  2. Rely on doRequestWithRetry: ensure read errors also trigger a retry (retry before reading the body, or treat read failure as retryable)
  3. Disable keep-alive reuse issues by tuning the transport's IdleConnTimeout or forcing a fresh connection on retry
  4. Check for intermediary proxies/LBs with short response timeouts

Example fix

// before
body, err := io.ReadAll(resp.Body)
if err != nil {
    return nil, fmt.Errorf("[%s] 读取响应体失败: %w", p.Name(), err)
}
// after
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
if err != nil {
    return nil, fmt.Errorf("[%s] 读取响应体失败(可重试): %w", p.Name(), err)
}
Defensive patterns

Strategy: retry

Validate before calling

// declare expected size upfront and cap the read:
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))

Try / catch

if err != nil {
    if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET) {
        // transient mid-body disconnect: safe to retry the whole request
        return retrySearch()
    }
    return nil, err
}

Prevention

When it happens

Trigger: io.ReadAll fails while draining resp.Body: unexpected EOF from a dropped connection, connection reset by peer mid-transfer, or TLS record errors on a reused connection.

Common situations: Flaky mobile/proxy networks; server or load balancer timeouts cutting large responses; aggressive keep-alive reuse hitting a server-side idle timeout; unstable container networking.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/6b485905a16ea4fe. Report an issue: GitHub.

Appendix: source

Thrown at plugin/meitizy/meitizy.go:185

	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Origin", FrontendURL)
	req.Header.Set("Referer", FrontendURL+"/")

	// 使用优化的客户端发送请求(带重试)
	resp, err := p.doRequestWithRetry(req, p.optimizedClient)
	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)
	}

	// 读取响应体
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[%s] 读取响应体失败: %w", p.Name(), err)
	}

	// 解析JSON响应
	var apiResp searchResponse
	if err := json.Unmarshal(body, &apiResp); err != nil {
		return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
	}

	// 转换为标准格式
	results := p.convertToSearchResults(apiResp.Data)

	// 关键词过滤(标准网盘插件需要过滤)
	filteredResults := plugin.FilterResultsByKeyword(results, keyword)

	return filteredResults, nil
}

// convertToSearchResults 将API响应转换为标准SearchResult格式

View on GitHub (pinned to beaa561337)