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
- Let aiohttp set Content-Length automatically from the body — don't set it manually.
- If you must set it, always use str(int(value)) to guarantee a clean integer string.
- Validate the value is a non-negative integer before assigning to the header.
- 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
- Avoid setting Content-Length manually; let aiohttp derive it from the body.
- Always coerce to int then str: str(int(value)).
- Strip any stale Content-Length from templated headers.
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
- chunked can not be set if Content-Length header is set
- chunked can not be set if "Transfer-Encoding: chunked"…
- compress can not be set if Content-Encoding header is set
- compress must be one of True, False, 'deflate', or 'gzip'
- invalid Content-Length
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)