aio-libs/aiohttp · error · BadStatusLine
Bad status line
Error message
Bad status line {line!r} What it means
Raised when the protocol version token in the request line does not match VERSRE HTTP/(\d)\.(\d). The request line must terminate with a valid 'HTTP/x.y' token (e.g. HTTP/1.0, HTTP/1.1, HTTP/2.0).
Solutions
- Send 'HTTP/1.0' or 'HTTP/1.1' (or 'HTTP/2.0') exactly as the version token.
- Use a real HTTP client library instead of raw sockets.
- Audit any code that builds request lines by hand.
Example fix
# before sock.send(b'GET / HTTP1.1\r\n\r\n') # after sock.send(b'GET / HTTP/1.1\r\n\r\n')
Defensive patterns
Strategy: validation
Validate before calling
import re
_VERS = re.compile(r'HTTP/(\d)\.(\d)', re.ASCII)
def format_request_line(method: str, path: str) -> str:
if not _VERS.fullmatch('HTTP/1.1'):
raise ValueError
return f'{method} {path} HTTP/1.1' Type guard
import re
_VERS = re.compile(r'HTTP/(\d)\.(\d)', re.ASCII)
def is_valid_version(tok: str) -> bool:
return bool(_VERS.fullmatch(tok)) Try / catch
from aiohttp.http_exceptions import BadStatusLine, BadHttpMessage
try:
...parse...
except BadStatusLine as e:
# e.line holds the offending request line; drop the peer
... Prevention
- Always use the canonical 'HTTP/1.1' version token in hand-crafted requests.
- Avoid building request lines by concatenating untrusted input.
When it happens
Trigger: A version that is missing or malformed: 'GET / HTTP1.1', 'GET / 1.1', 'GET / HTTP/1', 'GET / FTP/1.0', or 'GET /' with no version.
Common situations: Hand-crafted requests via raw sockets or netcat, custom clients with wrong version formatting, fuzzing, proxies injecting junk into the request line.
Related errors
- Bad HTTP method in status line
- Bad HTTP method in status line
- Bad line ending, expected CRLF
- Duplicate ' ' header found.
- Invalid HTTP header
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/a71b654e08536454.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/http_parser.py:671
"""
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 d041d4d0fd)