aio-libs/aiohttp · error · TransferEncodingError

Not enough data to satisfy transfer length header.

Error message

Not enough data to satisfy transfer length header.

What it means

Raised as TransferEncodingError (a BadHttpMessage subclass) when a chunked-encoded body stream reaches EOF before the terminating zero-length chunk (and trailers). HttpPayloadParser.feed_eof, in the PARSE_CHUNKED branch, raises unconditionally: a properly terminated chunked body finishes via the size-0 chunk path, never via EOF. So hitting EOF in this state means the chunked stream was truncated.

Solutions

  1. Ensure the sender of a chunked body always emits the final '0\r\n\r\n' before closing.
  2. On the receiving side, treat this as a fatal connection error; reconnect and retry idempotent requests.
  3. Inspect intermediaries (proxies, load balancers) that may strip or truncate the terminating chunk.
  4. If streaming cannot be guaranteed to complete, consider a different framing or reconnect logic.
  5. Check the sender's logs for unhandled exceptions during chunk production.

Example fix

# before (server-side handler crashes before write_eof)
resp = web.StreamResponse(headers={'Transfer-Encoding': 'chunked'})
await resp.prepare(request)
await resp.write(b'some data')
# ... exception raised here, connection closes without final chunk

# after (ensure write_eof runs in a finally)
resp = web.StreamResponse()
await resp.prepare(request)
try:
    await resp.write(b'some data')
finally:
    await resp.write_eof()
Defensive patterns

Strategy: retry

Try / catch

from aiohttp.http_exceptions import TransferEncodingError
import aiohttp

async def fetch_chunked(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 TransferEncodingError as e:
            last = e
            await asyncio.sleep(2 ** i)
    raise last

Prevention

When it happens

Trigger: A request/response declares 'Transfer-Encoding: chunked' but the connection closes before the final '0\r\n\r\n' terminator arrives. Fires when feed_eof is called while _type == ParseState.PARSE_CHUNKED.

Common situations: The server/client closes the connection mid-chunk without sending the terminating zero-size chunk; a proxy or CDN severing a streaming response; network resets; a misbehaving streaming endpoint that crashes before finishing the chunked frame; clients that give up reading and abort.

Related errors


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

Appendix: source

Thrown at aiohttp/http_parser.py:934

            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";"
    ) -> tuple[PayloadState, bytes]:
        """Receive a chunk of data to process.

        Return:
            PayloadState - The current state of payload processing.
                           This function may be called with empty bytes after returning
                           PAYLOAD_HAS_PENDING_INPUT to continue processing after a pause.
            bytes - If payload is complete, this is the unconsumed bytes intended for the
                    next message/payload, b"" otherwise.
        """
        # Read specified amount of bytes
        if self._type == ParseState.PARSE_LENGTH:
            if self._chunk_tail:

View on GitHub (pinned to d041d4d0fd)