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 when both Transfer-Encoding and Content-Length are present in the same message. RFC 9110 section 6.6.3 forbids the combination because it enables request smuggling (front/back parsers disagree on body framing). aiohttp rejects it outright rather than trying to disambiguate.

Solutions

  1. Send exactly one of Transfer-Encoding or Content-Length.
  2. When sending chunked, do not set Content-Length.
  3. Audit any proxy / header-rewriting middleware that may add the other header.

Example fix

# before
headers['Transfer-Encoding'] = 'chunked'
headers['Content-Length'] = '100'   # conflict

# after - choose one
headers['Transfer-Encoding'] = 'chunked'  # del headers['Content-Length']
Defensive patterns

Strategy: validation

Validate before calling

def framing_is_unambiguous(headers) -> bool:
    names = {k.lower() for k in headers}
    return not ({'transfer-encoding', 'content-length'} <= names)

Type guard

def only_one_framing_header(headers) -> bool:
    names = {k.lower() for k in headers}
    return not ({'transfer-encoding', 'content-length'} <= names)

Try / catch

from aiohttp.http_exceptions import BadHttpMessage
try:
    ...parse...
except BadHttpMessage as e:
    if 'Transfer-Encoding' in str(e):
        transport.close()  # likely smuggling; do not retry

Prevention

When it happens

Trigger: A peer sends a request or response carrying both Transfer-Encoding (e.g. chunked) and Content-Length.

Common situations: Proxies forwarding a client's TE and adding CL (or vice versa), caches that mangle headers, deliberate smuggling attacks, buggy custom servers combining both.

Related errors


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

Appendix: source

Thrown at aiohttp/http_parser.py:633

                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 d041d4d0fd)