aio-libs/aiohttp · error · BadHttpMessage

Request has duplicate `chunked` Transfer-Encoding

Error message

Request has duplicate `chunked` Transfer-Encoding

What it means

Raised as BadHttpMessage (HTTP 400) when the Transfer-Encoding header contains the 'chunked' coding more than once. RFC 9112 §7.1 forbids applying the chunked transfer coding more than once to a message body. The guard lives in HttpRequestParser._is_chunked_te: it splits TE on commas and counts case-insensitive ASCII 'chunked' tokens; more than one raises.

Solutions

  1. Send Transfer-Encoding with at most one 'chunked' token (it must be the last token if present).
  2. Fix the proxy/middleware that is appending 'chunked' a second time to the TE header.
  3. If you control the client, ensure the TE header is built once and not concatenated across hops.
  4. Validate outgoing TE headers before sending: a single trailing 'chunked' only.

Example fix

// before
Transfer-Encoding: chunked, chunked\r\n

// after
Transfer-Encoding: chunked\r\n
Defensive patterns

Strategy: validation

Validate before calling

def normalize_transfer_encoding(te: str) -> str:
    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:
        # collapse duplicates: keep a single trailing 'chunked'
        parts = [p for p in parts if not (p.isascii() and p.lower() == 'chunked')]
        parts.append('chunked')
    return ','.join(parts)

Try / catch

from aiohttp.http_exceptions import BadHttpMessage

try:
    parser.feed_data(raw)
except BadHttpMessage as e:
    respond_400(str(e))  # duplicate chunked / invalid TE

Prevention

When it happens

Trigger: A request with 'Transfer-Encoding: chunked, chunked' (or any comma-separated list containing 'chunked' twice). Parsed by _is_chunked_te before the body is read; triggered during parse_headers when the TE header is present.

Common situations: A buggy intermediary (proxy, middleware) that appends 'chunked' to an already-chunked TE header; fuzzing/security scanners probing encoding-handling bugs; misconfigured reverse proxy double-encoding chunked transfers; an HTTP/1.1 client built manually that stacks the encoding.

Related errors


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

Appendix: source

Thrown at aiohttp/http_parser.py:747

            path,
            version_o,
            headers,
            raw_headers,
            close,
            compression,
            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.

View on GitHub (pinned to d041d4d0fd)