aio-libs/aiohttp · error · ValueError

Invalid Content-Length header

Error message

Invalid Content-Length header: {content_length_hdr!r}

What it means

Raised by ClientRequest._get_content_length() when a manually-set Content-Length header value does not match _DIGITS_RE (one or more ASCII digits). Non-numeric, negative, fractional, or whitespace-containing values are rejected with ValueError. Headers set automatically from bodies are always valid; this fires only when a caller manually injects a malformed Content-Length.

Solutions

  1. Let aiohttp set Content-Length automatically from the body — don't set it manually.
  2. If you must set it, always use str(int(value)) to guarantee a clean integer string.
  3. Validate the value is a non-negative integer before assigning to the header.
  4. Remove any existing Content-Length header before re-setting it to avoid stale duplicates.

Example fix

# before
headers['Content-Length'] = payload_size  # payload_size='12.5' -> ValueError

# after
headers['Content-Length'] = str(int(payload_size))
# or, better, omit the header and let aiohttp compute it from data=
Defensive patterns

Strategy: validation

Validate before calling

def set_content_length(headers, value):
    n = int(value)  # raises early if non-numeric
    if n < 0:
        raise ValueError('Content-Length must be non-negative')
    headers['Content-Length'] = str(n)

Type guard

import re
_DIGITS = re.compile(r'\d+', re.ASCII)
def is_valid_content_length_header(value: str) -> bool:
    return bool(_DIGITS.fullmatch(str(value)))

Try / catch

try:
    await session.post(url, headers=headers, data=body)
except ValueError as e:
    if 'Content-Length' in str(e):
        headers.pop('Content-Length', None)  # let aiohttp compute it
        await session.post(url, headers=headers, data=body)
    else:
        raise

Prevention

When it happens

Trigger: Manually setting headers['Content-Length'] to a non-integer string like 'abc', '12.5', '-1', ' 10 ', or '0x10' before sending. _get_content_length() runs the digits regex and raises ValueError.

Common situations: Injecting Content-Length from untrusted input without casting to int; copy-paste of a header value with units ('100 bytes'); locale/formatting producing non-ASCII digits; off-by-one string slicing corrupting the value.

Related errors


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

Appendix: 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 d041d4d0fd)