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
- Include a well-formed version token ('HTTP/1.1' or 'HTTP/1.0') as the third space-separated element.
- Use a real HTTP client that formats the request line correctly.
- 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
- Always include a well-formed HTTP/x.y version token in the request line
- Use an HTTP library that formats the request line
- Negotiate HTTP/2 via ALPN, not by editing the request line
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
- Bad HTTP method in status line {line!r}
- Bad line ending, expected CRLF
- {path}
- Got more than {limit} bytes when reading: {line!r}.
- Duplicate '{name}' header found.
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/a71b654e08536454.json.
Report an issue: GitHub.