aio-libs/aiohttp · error · BadStatusLine

Bad status line {line!r}

Error message

Bad status line {line!r}

What it means

Raised by HttpRequestParser.parse_message (aiohttp/http_parser.py:669) when the version token (third element of the request line) does not fullmatch VERSRE (HTTP/(\d)\.(\d)). Caught forms include missing 'HTTP/' prefix ('1.1'), missing minor version ('HTTP/2'), wrong prefix ('FOO/1.1'), or garbage. Note 'HTTP/2.0' DOES match and yields HttpVersion(2,0); 'HTTP/3' does NOT (no dot).

Source

Thrown at aiohttp/http_parser.py:669

    """

    def parse_message(self, lines: list[bytes]) -> RawRequestMessage:
        # request line
        line = lines[0].decode("utf-8", "surrogateescape")
        try:
            method, path, version = line.split(" ", maxsplit=2)
        except ValueError:
            raise BadHttpMethod(line) from None

        # method
        if not TOKENRE.fullmatch(method):
            raise BadHttpMethod(method)
        method = method.upper()

        # version
        match = VERSRE.fullmatch(version)
        if match is None:
            raise BadStatusLine(line)
        version_o = HttpVersion(int(match.group(1)), int(match.group(2)))

        if method == "CONNECT":
            # authority-form,
            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3
            url = URL.build(authority=path, encoded=True)
        elif path.startswith("/"):
            # origin-form,
            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1
            path_part, _hash_separator, url_fragment = path.partition("#")
            path_part, _question_mark_separator, qs_part = path_part.partition("?")

            # NOTE: `yarl.URL.build()` is used to mimic what the Cython-based
            # NOTE: parser does, otherwise it results into the same
            # NOTE: HTTP Request-Line input producing different
            # NOTE: `yarl.URL()` objects
            url = URL.build(
                path=path_part,

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Include a well-formed version token ('HTTP/1.1' or 'HTTP/1.0') as the third space-separated element.
  2. Use a real HTTP client that formats the request line correctly.
  3. If supporting HTTP/2, ensure the client negotiates via ALPN rather than sending a custom request line.

Example fix

# before - missing/wrong version token
sock.send(b'GET / 1.1\r\n\r\n')        # no 'HTTP/' prefix
sock.send(b'GET / HTTP/2\r\n\r\n')     # no minor version
# after
sock.send(b'GET / HTTP/1.1\r\nHost: x\r\n\r\n')
Defensive patterns

Strategy: validation

Validate before calling

import re
_VERSRE = re.compile(r'HTTP/(\d)\.(\d)\Z')
def valid_http_version(token: str) -> bool:
    m = _VERSRE.fullmatch(token)
    if not m:
        return False
    return tuple(int(x) for x in m.groups()) in {(1,0),(1,1),(2,0)}
assert valid_http_version(version_token)

Prevention

When it happens

Trigger: A request line like 'GET / 1.1' (no HTTP/), 'GET / HTTP/2' (no minor), 'GET / FOO/1.1', or 'GET / HTTP/1.1 ' (trailing space - though split would keep it in the version token). Fires whenever the version token fails the strict HTTP/x.y pattern.

Common situations: Raw socket clients omitting the version, non-HTTP protocols reusing the parser, hand-built requests with typos, or HTTP/2-only clients that send a non-standard preface on an HTTP/1.1 connection.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/a71b654e08536454.json. Report an issue: GitHub.