aio-libs/aiohttp · error · ContentLengthError

Not enough data to satisfy content length header

Error message

Not enough data to satisfy content length header (received {received} of {self._length_expected} bytes).

What it means

Raised as ContentLengthError (a BadHttpMessage subclass) when the connection reaches EOF but fewer body bytes were received than declared by Content-Length. HttpPayloadParser.feed_eof, in the PARSE_LENGTH branch, checks if self._length is still positive (bytes still owed); if so it raises with the received vs expected counts. This indicates a truncated message body.

Solutions

  1. If you are the sender, ensure you write exactly Content-Length body bytes before closing.
  2. On the receiving side, treat this as a connection-level error and retry the request idempotently.
  3. Verify no intermediary (proxy, firewall) is truncating large responses.
  4. If the body length is genuinely unknown, use chunked transfer-encoding instead of Content-Length.
  5. Check server logs for exceptions during response streaming.

Example fix

# before (server computes Content-Length wrong)
return web.Response(body=data, headers={'Content-Length': str(len(data) + 10)})

# after (let aiohttp set Content-Length, or use chunked)
return web.Response(body=data)  # Content-Length auto-set correctly
Defensive patterns

Strategy: retry

Validate before calling

async def safe_read(resp) -> bytes:
    try:
        return await resp.read()
    except Exception:
        # ContentLengthError indicates truncation; caller decides on retry
        raise

Try / catch

from aiohttp.http_exceptions import ContentLengthError
import aiohttp

async def fetch_full(url, *, attempts=3):
    last = None
    for i in range(attempts):
        try:
            async with aiohttp.ClientSession() as s:
                async with s.get(url) as r:
                    return await r.read()
        except ContentLengthError as e:
            last = e
            await asyncio.sleep(2 ** i)
    raise last

Prevention

When it happens

Trigger: A response/request has a Content-Length: N header, but the stream closes (feed_eof) before N body bytes arrive. Fires when _type == ParseState.PARSE_LENGTH and self._length > 0 at EOF.

Common situations: The server (or client) closes the connection mid-body before sending all Content-Length bytes; a proxy truncating the response; network resets/instability dropping the tail; a server that sets Content-Length but then errors out sending fewer bytes; chunked encoding incorrectly absent; a buggy upstream that miscomputes Content-Length.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/118473f5c82745c0. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/http_parser.py:921

    def pause_reading(self) -> None:
        self._paused = True

    def feed_eof(self) -> None:
        if self._type == ParseState.PARSE_UNTIL_EOF:
            self._eof_pending = True
            while self._more_data_available:
                if self._paused:
                    self._paused = False
                    return  # Will resume via feed_data(b"") later
                self._more_data_available = self.payload.feed_data(b"")
            self.payload.feed_eof()
            self.done = True
            self._eof_pending = False
        elif self._type == ParseState.PARSE_LENGTH:
            if self._length:
                received = self._length_expected - self._length
                raise ContentLengthError(
                    f"Not enough data to satisfy content length header "
                    f"(received {received} of {self._length_expected} bytes)."
                )
            # Body has already been received, but parser paused.
            while self._more_data_available:
                if self._paused:
                    self._paused = False
                    return  # Will resume via feed_data(b"") later
                self._more_data_available = self.payload.feed_data(b"")
            self.payload.feed_eof()
            self.done = True
        elif self._type == ParseState.PARSE_CHUNKED:
            raise TransferEncodingError(
                "Not enough data to satisfy transfer length header."
            )

    def feed_data(
        self, chunk: bytes, SEP: _SEP = b"\r\n", CHUNK_EXT: bytes = b";"

View on GitHub (pinned to d041d4d0fd)