fish2018/pansou · error
[ ] 读取流式结果失败
Error message
[%s] 读取流式结果失败: %w
What it means
After scanning the SSE/streaming response line by line, searchImpl checks scanner.Err(). If the stream read failed (connection reset mid-stream, chunk decode error, line exceeding buffer) AND no items were collected, this error is returned. If some items were already parsed, the error is swallowed in favor of partial results.
Solutions
- Unwrap the error to distinguish connection reset from buffer overflow (`bufio.Scanner: token too long`).
- If token too long, raise the scanner buffer: scanner.Buffer(make([]byte, 0, 64*1024), largerMax).
- Retry the search — a mid-stream reset is often transient.
- Check intermediary proxies/CDN timeouts that may cut long-lived streaming responses.
Example fix
// before: fixed 1MB cap scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) // after: larger max line for big payloads scanner.Buffer(make([]byte, 0, 64*1024), 8*1024*1024)
Defensive patterns
Strategy: fallback
Try / catch
results, err := p.searchImpl(client, keyword)
if err != nil && strings.Contains(err.Error(), "读取流式结果失败") {
log.Printf("stream truncated; returning cached results: %v", err)
return cache.Get(keyword)
} Prevention
- Size the scanner buffer above the largest expected line (raise the 1MB cap if payloads can be big).
- Treat partial stream results as acceptable and return them instead of failing.
- Watch for proxies/CDNs with short idle timeouts that cut long SSE streams.
- Retry once automatically on mid-stream resets.
When it happens
Trigger: bufio.Scanner errors while reading the streaming body — connection dropped by the server mid-stream, or a single line exceeding the 1MB scanner buffer.
Common situations: Upstream closes the connection abruptly under load; a proxy/CDN truncates the SSE stream; extremely large response lines exceeding the 1MB max token size.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/76deff6aca87f486.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/jupansou/jupansou.go:153
if payload == "" || payload == "[DONE]" {
continue
}
var item juPansouStreamItem
if err := json.Unmarshal([]byte(payload), &item); err != nil {
continue
}
item.Title = strings.TrimSpace(item.Title)
item.Name = strings.TrimSpace(item.Name)
item.URL = strings.TrimSpace(item.URL)
if item.Title == "" || item.URL == "" {
continue
}
items = append(items, item)
}
if err := scanner.Err(); err != nil && len(items) == 0 {
return nil, fmt.Errorf("[%s] 读取流式结果失败: %w", p.Name(), err)
}
// The stream may contain broad third-party lines. Filter by title before
// exchanging encrypted URLs to avoid unnecessary transfer requests.
keywordLower := strings.ToLower(strings.TrimSpace(keyword))
filteredItems := items[:0]
for _, item := range items {
if keywordLower == "" || strings.Contains(strings.ToLower(item.Title), keywordLower) {
filteredItems = append(filteredItems, item)
}
}
results := p.exchangeItems(client, filteredItems)
return plugin.FilterResultsByKeyword(results, keyword), nil
}
func (p *JuPansouPlugin) ensureSearchSession(client *http.Client) error {
ctx, cancel := context.WithTimeout(context.Background(), jupansouTimeout)View on GitHub (pinned to beaa561337)