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

  1. Unwrap the error to distinguish connection reset from buffer overflow (`bufio.Scanner: token too long`).
  2. If token too long, raise the scanner buffer: scanner.Buffer(make([]byte, 0, 64*1024), largerMax).
  3. Retry the search — a mid-stream reset is often transient.
  4. 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

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)