aio-libs/aiohttp · error · BadHttpMessage
Request has invalid `Transfer-Encoding`
Error message
Request has invalid `Transfer-Encoding`
What it means
Raised as BadHttpMessage (HTTP 400) when a Transfer-Encoding header is present and its last comma-separated token is not 'chunked'. RFC 9112 §6.3 requires that, when chunked is used, it must be the final transfer coding. The guard in _is_chunked_te returns True only if the last token (ASCII, case-insensitive) equals 'chunked'; otherwise it raises this error.
Solutions
- Ensure the Transfer-Encoding header, when present, ends with 'chunked' as its last token.
- Use Content-Encoding (not Transfer-Encoding) for gzip/deflate/br body compression.
- Remove the Transfer-Encoding header entirely if you want a fixed-length body and send Content-Length instead.
- Audit intermediaries that rewrite TE to make sure they preserve chunked-last ordering.
Example fix
// before (non-chunked last token) Transfer-Encoding: gzip\r\n // after (use Content-Encoding for compression) Content-Encoding: gzip\r\nContent-Length: 123\r\n // or, if chunked streaming is intended, chunked must be last Transfer-Encoding: gzip, chunked\r
Defensive patterns
Strategy: validation
Validate before calling
def is_valid_te(te: str) -> bool:
parts = [p.strip(" \t") for p in te.split(',')]
last = parts[-1]
return last.isascii() and last.lower() == 'chunked'
# use Content-Encoding for compression, not a non-chunked Transfer-Encoding Try / catch
from aiohttp.http_exceptions import BadHttpMessage
try:
parser.feed_data(raw)
except BadHttpMessage as e:
respond_400(str(e)) Prevention
- Use Content-Encoding (not Transfer-Encoding) for body compression.
- Ensure 'chunked', when used, is the last TE token.
- Drop TE entirely in favor of Content-Length for fixed-length bodies.
When it happens
Trigger: A request with a Transfer-Encoding whose last token is something other than 'chunked' (e.g. 'gzip', 'identity', or an unknown coding), or where 'chunked' appears but is not last. Fires inside _is_chunked_te when the TE header is non-empty and does not end in chunked.
Common situations: Client/intermediary sends 'Transfer-Encoding: gzip' (non-standard, should use Content-Encoding); a custom coding name with a typo; a server/old client using deprecated encodings; a proxy reordering TE tokens so chunked is not last; fuzzers probing the parser.
Related errors
- Request has duplicate `chunked` Transfer-Encoding
- Not enough data to satisfy transfer length header.
- chunked can not be set if Content-Length header is set
- chunked can not be set if "Transfer-Encoding: chunked"…
- {path}
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/cf31f2735c6a95d7.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/http_parser.py:753
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 d041d4d0fd)