aio-libs/aiohttp · error · BadHttpMessage

Too many trailers received

Error message

Too many trailers received

What it means

Raised as BadHttpMessage (HTTP 400) when the number of trailer lines in a chunked body exceeds _max_trailers (default 128, derived from max_headers). After each trailer line is appended, the parser checks 'if len(self._trailer_lines) > self._max_trailers' and raises. This bounds the count of trailer headers to prevent resource exhaustion.

Solutions

  1. Limit the number of trailer headers you send (<=128 by default).
  2. If more trailers are genuinely needed, raise max_headers on the parser (ClientSession(..., max_headers=N) or RequestHandler kwargs).
  3. Strip unnecessary trailers at proxies before forwarding.
  4. Treat unbounded trailer counts from untrusted clients as malicious.

Example fix

# before
session = aiohttp.ClientSession()

# after (raise trailer/header cap if needed)
session = aiohttp.ClientSession(max_headers=256)
Defensive patterns

Strategy: validation

Validate before calling

def trailer_count_ok(trailers: list, max_trailers: int = 128) -> bool:
    return len(trailers) <= max_trailers

Try / catch

from aiohttp.http_exceptions import BadHttpMessage

try:
    parser.feed_data(raw)
except BadHttpMessage as e:
    respond_400(str(e))  # 'Too many trailers received'

Prevention

When it happens

Trigger: A chunked body whose trailer section contains more than max_trailers (default 128) lines before the terminating empty line. Fires inside PARSE_TRAILERS as lines accumulate.

Common situations: An attacker flooding the trailer section with many headers (resource-exhaustion); a misbehaving intermediary appending per-hop trailer headers; a server/client streaming many trailer headers by mistake; legitimate high trailer counts exceeding the default 128.

Related errors


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

Appendix: source

Thrown at aiohttp/http_parser.py:1098

                                "Bad trailer line ending, expected CRLF"
                            )
                            set_exception(self.payload, exc)
                            raise exc
                        self._chunk_tail = chunk
                        return PayloadState.PAYLOAD_NEEDS_INPUT, b""

                    line = chunk[:pos]
                    chunk = chunk[pos + len(SEP) :]
                    if SEP == b"\n":  # For lax response parsing
                        line = line.rstrip(b"\r")

                    if len(line) > self._max_field_size:
                        raise LineTooLong(line[:100] + b"...", self._max_field_size)

                    self._trailer_lines.append(line)

                    if len(self._trailer_lines) > self._max_trailers:
                        raise BadHttpMessage("Too many trailers received")

                    # \r\n\r\n found, end of stream
                    if self._trailer_lines[-1] == b"":
                        # Headers and trailers are defined the same way,
                        # so we reuse the HeadersParser here.
                        try:
                            trailers, raw_trailers = self._headers_parser.parse_headers(
                                self._trailer_lines
                            )
                        finally:
                            self._trailer_lines.clear()
                        self.payload.feed_eof()
                        return PayloadState.PAYLOAD_COMPLETE, chunk

        # Read all bytes until eof
        elif self._type == ParseState.PARSE_UNTIL_EOF:
            self._more_data_available = self.payload.feed_data(chunk)
            while self._more_data_available:

View on GitHub (pinned to d041d4d0fd)