Tencent/WeKnora · error

read body failed: %w

Error message

read body failed: %w

What it means

Returned when reading the HTTP response body fails after a 2xx status. The body is read through io.LimitReader capped at maxSize (10 MB for feeds, 5 MB for articles), so this error comes from a broken/truncated connection, an unexpected EOF, a decompression failure (e.g. malformed gzip), or the request context timing out mid-read.

Source

Thrown at internal/datasource/connector/rss/client.go:89

	}
	if req.Header.Get("Accept") == "" {
		req.Header.Set("Accept",
			"application/rss+xml, application/atom+xml, application/xml, text/xml, application/json, text/html;q=0.9, */*;q=0.8")
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("fetch failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("HTTP %d %s", resp.StatusCode, resp.Status)
	}

	body, err := io.ReadAll(io.LimitReader(resp.Body, maxSize))
	if err != nil {
		return nil, fmt.Errorf("read body failed: %w", err)
	}
	return body, nil
}

// fetchFeed retrieves the raw bytes of a feed document.
func (c *client) fetchFeed(ctx context.Context, feedURL string) ([]byte, error) {
	return c.fetch(ctx, feedURL, maxFeedSize, true)
}

// extractArticle fetches an article page and returns the readability-cleaned
// main content as HTML, plus the extracted title (may be empty). Returns an
// error if the page can't be fetched or no readable content is found, so the
// caller can fall back to feed-provided content.
func (c *client) extractArticle(ctx context.Context, articleURL string) (contentHTML, title string, err error) {
	body, err := c.fetch(ctx, articleURL, maxArticleSize, false)
	if err != nil {
		return "", "", err
	}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Retry the fetch — truncation is usually transient; wrap the call in a retry with backoff.
  2. If timeouts recur, reduce the amount fetched (trim the feed) or increase requestTimeout; note the maxSize LimitReader caps memory but not time.
  3. Bypass or fix a misconfigured proxy/CDN that is corrupting Content-Encoding; test with curl --compressed.
  4. Check server-side logs on the feed host for crashes or connection resets.

Example fix

// before
data, err := cli.fetchFeed(ctx, feedURL)
// after
var data []byte
var err error
for i := 0; i < 3; i++ {
    data, err = cli.fetchFeed(ctx, feedURL)
    if err == nil || !isTransientReadError(err) {
        break
    }
    time.Sleep(time.Duration(1<<i) * time.Second)
}
Defensive patterns

Strategy: retry

Try / catch

data, err := cli.fetchFeed(ctx, feedURL)
if err != nil {
    if isTruncated(err) { // io.ErrUnexpectedEOF / context deadline in message
        ctx2, cancel := context.WithTimeout(ctx, 60*time.Second)
        defer cancel()
        data, err = cli.fetchFeed(ctx2, feedURL)
    }
    if err != nil {
        return fmt.Errorf("feed %s: %w", feedURL, err)
    }
}

Prevention

When it happens

Trigger: The server closes the connection before sending the full body; a proxy or CDN truncates the response; the response uses gzip/deflate/brotli with corrupt bytes; the 20s context deadline expires while streaming a large body.

Common situations: Flaky mobile/behind-NAT feed hosts dropping connections; misconfigured CDNs sending a Content-Encoding the client cannot decompress; very large feeds on slow links hitting the 20-second timeout; load balancers with short idle timeouts.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/33035a5df03c4c0d. Report an issue: GitHub.