aio-libs/aiohttp · error · BadHttpMessage
Transfer-Encoding can't be present with Content-Length
Error message
Transfer-Encoding can't be present with Content-Length
What it means
Raised by HttpParser.parse_headers (aiohttp/http_parser.py:631) when a message carries BOTH Transfer-Encoding and Content-Length. RFC 9112 section 6.3 forbids the combination because it creates the classic request-smuggling ambiguity (which header wins?). aiohttp rejects it outright in both request and response parsers, regardless of lax mode.
Source
Thrown at aiohttp/http_parser.py:631
close_conn = False
# https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols
if "upgrade" in conn_tokens and headers.get(hdrs.UPGRADE):
upgrade = True
# encoding
enc = headers.get(hdrs.CONTENT_ENCODING, "")
if enc.isascii() and enc.lower() in {"gzip", "deflate", "br", "zstd"}:
encoding = enc
# chunking
te = headers.get(hdrs.TRANSFER_ENCODING)
if te is not None:
if self._is_chunked_te(te):
chunked = True
if hdrs.CONTENT_LENGTH in headers:
raise BadHttpMessage(
"Transfer-Encoding can't be present with Content-Length",
)
return (headers, raw_headers, close_conn, encoding, upgrade, chunked)
def set_upgraded(self, val: bool) -> None:
"""Set connection upgraded (to websocket) mode.
:param bool val: new state.
"""
self._upgraded = val
class HttpRequestParser(HttpParser[RawRequestMessage]):
"""Read request status line.
Exception .http_exceptions.BadStatusLine
could be raised in case of any errors in status line.View on GitHub (pinned to c0ef574e29)
Solutions
- Send exactly one of Transfer-Encoding: chunked OR Content-Length - never both.
- If you operate a proxy, strip one before forwarding (prefer normalizing to a single mechanism).
- Let aiohttp handle chunked encoding automatically when you stream a body; do not also set Content-Length.
- On the server, aiohttp returns 400; investigate the upstream proxy chain.
Example fix
# before - both set
headers['Transfer-Encoding'] = 'chunked'
headers['Content-Length'] = str(len(body)) # conflict!
# after - pick one; when chunked, drop Content-Length
headers['Transfer-Encoding'] = 'chunked'
headers.pop('Content-Length', None) Defensive patterns
Strategy: validation
Validate before calling
def no_te_cl_conflict(headers) -> bool:
has_te = any(k.lower() == 'transfer-encoding' for k in headers)
has_cl = any(k.lower() == 'content-length' for k in headers)
return not (has_te and has_cl)
assert no_te_cl_conflict(outgoing), 'TE and CL both present' Prevention
- Never set both Transfer-Encoding and Content-Length on the same message
- Strip one at every proxy hop
- Let the library manage the framing when you stream a body
When it happens
Trigger: Any request or response whose headers contain both Transfer-Encoding (any value) and Content-Length. Even 'Transfer-Encoding: identity' alongside Content-Length triggers it because the check keys off the mere presence of both headers.
Common situations: Request-smuggling attacks against proxy chains, HTTP/1.1 downgrade attacks in front of HTTP/2 backends, or a proxy that adds one header without stripping the other when forwarding.
Related errors
- Duplicate '{name}' header found.
- Request has duplicate `chunked` Transfer-Encoding
- Method cannot contain non-token characters {method!r} (found
- Invalid Content-Length header: {content_length_hdr!r}
- chunked can not be set if "Transfer-Encoding: chunked" heade
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/949d8f3a00373c20.json.
Report an issue: GitHub.