aio-libs/aiohttp · error · ValueError

Invalid Content-Length header: {content_length_hdr!r}

Error message

Invalid Content-Length header: {content_length_hdr!r}

What it means

Raised as ValueError in ClientRequest._get_content_length() when a Content-Length header is present but its value is not all ASCII digits (fails _DIGITS_RE.fullmatch). HTTP forbids non-digit Content-Length values; this guard catches malformed or tampered header values before the request is sent.

Source

Thrown at aiohttp/client_reqrep.py:864

            self.headers[hdrs.AUTHORIZATION] = encode_basic_auth(
                url.user or "", url.password or ""
            )

    def _reset_writer(self, _: object = None) -> None:
        self._writer_task = None

    def _get_content_length(self) -> int | None:
        """Extract and validate Content-Length header value.

        Returns parsed Content-Length value or None if not set.
        Raises ValueError if header exists but cannot be parsed as an integer.
        """
        if hdrs.CONTENT_LENGTH not in self.headers:
            return None

        content_length_hdr = self.headers[hdrs.CONTENT_LENGTH]
        if not _DIGITS_RE.fullmatch(content_length_hdr):
            raise ValueError(f"Invalid Content-Length header: {content_length_hdr!r}")
        return int(content_length_hdr)

    @property
    def _writer(self) -> asyncio.Task[None] | None:
        return self._writer_task

    @_writer.setter
    def _writer(self, writer: asyncio.Task[None]) -> None:
        if self._writer_task is not None:
            self._writer_task.remove_done_callback(self._reset_writer)
        self._writer_task = writer
        writer.add_done_callback(self._reset_writer)

    def is_ssl(self) -> bool:
        return self.url.scheme in _SSL_SCHEMES

    @property
    def ssl(self) -> "SSLContext | bool | Fingerprint":

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Let aiohttp compute Content-Length automatically from the body instead of setting it manually.
  2. If you must set it, ensure the value is a clean decimal integer string.
  3. Strip whitespace and remove duplicate headers before assignment.
  4. When forwarding headers, collapse duplicates instead of concatenating with commas.

Example fix

# before
headers['Content-Length'] = str(len(body)) + ' '   # trailing space
headers['Content-Length'] = '0x10'
# after
# (preferred) omit Content-Length; aiohttp sets it from the body
# or
headers['Content-Length'] = str(int(len(body)))
Defensive patterns

Strategy: validation

Validate before calling

import re
if 'Content-Length' in headers:
    val = headers['Content-Length']
    assert re.fullmatch(r'\d+', val), f'bad Content-Length {val!r}'

Type guard

def is_valid_content_length(value: str) -> bool:
    import re
    return bool(re.fullmatch(r'\d+', str(value)))

Prevention

When it happens

Trigger: Fires at line 862-864 when self.headers[CONTENT_LENGTH] is something like '10 ', '0x10', '1, 1' (comma from proxy chains), or ''. Most often triggered by manually set headers or headers injected by middleware.

Common situations: Manually setting Content-Length to a non-numeric value; duplicate Content-Length headers collapsing to '1, 1'; trailing whitespace from string concatenation; copying headers from another response verbatim.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/f8f21d513ceece10.json. Report an issue: GitHub.