aio-libs/aiohttp · error · BadHttpMessage
Request has invalid `Transfer-Encoding`
Error message
Request has invalid `Transfer-Encoding`
What it means
Raised by the request parser in HttpRequestParser._is_chunked_te when an inbound request carries a Transfer-Encoding header whose final token is not 'chunked'. aiohttp only supports chunked transfer-coding for request bodies; any other coding (or a TE that does not terminate in chunked) is rejected per RFC 9112 section 6.3.
Source
Thrown at aiohttp/http_parser.py:751
upgrade,
chunked,
url,
)
def _is_chunked_te(self, te: str) -> bool:
# https://www.rfc-editor.org/rfc/rfc9112#section-7.1-3
# "A sender MUST NOT apply the chunked transfer coding more
# than once to a message body"
parts = [p.strip(" \t") for p in te.split(",")]
chunked_count = sum(1 for p in parts if p.isascii() and p.lower() == "chunked")
if chunked_count > 1:
raise BadHttpMessage("Request has duplicate `chunked` Transfer-Encoding")
last = parts[-1]
# .lower() transforms some non-ascii chars, so must check first.
if last.isascii() and last.lower() == "chunked":
return True
# https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3
raise BadHttpMessage("Request has invalid `Transfer-Encoding`")
class HttpResponseParser(HttpParser[RawResponseMessage]):
"""Read response status line and headers.
BadStatusLine could be raised in case of any errors in status line.
Returns RawResponseMessage.
"""
protocol: "ResponseHandler"
# Lax mode should only be enabled on response parser.
lax = not DEBUG
def feed_data(
self,
data: bytes,
SEP: _SEP | None = None,View on GitHub (pinned to c0ef574e29)
Solutions
- Inspect the request's Transfer-Encoding header and ensure it ends with 'chunked' or remove it and use Content-Length instead.
- If you need a content encoding, use Content-Encoding (end-to-end) rather than Transfer-Encoding (hop-by-hop).
- On the server side, catch BadHttpMessage to return a clean 400 instead of crashing the handler.
Example fix
// before: client sends // Transfer-Encoding: gzip # after: use Content-Length or chunked # Content-Length: 42 (or) # Transfer-Encoding: chunked
Defensive patterns
Strategy: try-catch
Validate before calling
te = request.headers.get('Transfer-Encoding', '')
parts = [p.strip().lower() for p in te.split(',')]
if parts and parts[-1] != 'chunked' and te:
# reject before the parser does
raise ValueError('unsupported Transfer-Encoding') Type guard
def is_valid_request_te(te: str) -> bool:
if not te:
return True
parts = [p.strip().lower() for p in te.split(',')]
return parts.count('chunked') <= 1 and parts[-1] == 'chunked' Try / catch
from aiohttp.http_exceptions import BadHttpMessage
try:
await request.read()
except BadHttpMessage:
return web.Response(status=400, text='Bad Transfer-Encoding') Prevention
- Never set Transfer-Encoding manually from a client; let the library choose chunked vs Content-Length.
- Remember TE is hop-by-hop and stripped by proxies — debug with a raw TCP capture.
When it happens
Trigger: A client sends a request with Transfer-Encoding set to a non-chunked value (e.g. `Transfer-Encoding: gzip`, `compress`, or `identity`) or a list that does not end in `chunked`. The request parser calls _is_chunked_te and, because the last comma-separated token is not 'chunked', raises BadHttpMessage (HTTP 400).
Common situations: Misconfigured reverse proxies or custom HTTP clients injecting a raw Transfer-Encoding; curl/scripting that sets TE manually; interop with servers that send non-standard codings; testing against a server that forwards a hop-by-hop TE.
Related errors
- Request has duplicate `chunked` Transfer-Encoding
- Duplicate '{name}' header found.
- Bad line ending, expected CRLF
- Transfer-Encoding can't be present with Content-Length
- Missing 'Host' header in request.
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/cf31f2735c6a95d7.json.
Report an issue: GitHub.