aio-libs/aiohttp · error · InvalidHeader
Invalid HTTP header
Error message
Invalid HTTP header: {hdr!r} What it means
Raised by the header parser when a header line cannot be split on ':' (line.split(b':', 1) raises ValueError), i.e. the line contains no colon at all. RFC 9112 requires every field line be 'field-name ":" OWS field-value'. The offending raw line (bytes) is included in the error.
Solutions
- Inspect the raw request/response bytes around the reported line.
- Ensure every header line is 'name: value' with a literal colon.
- Validate at the trust boundary (reverse proxy/WAF) before aiohttp sees the bytes.
Example fix
# before sock.send(b'GET / HTTP/1.1\r\nHost example.com\r\n\r\n') # after sock.send(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
Defensive patterns
Strategy: validation
Validate before calling
def header_line_ok(line: bytes) -> bool:
# strict: name must be a token and a colon must separate name/value
return b':' in line and not line[:1].isspace() Type guard
def has_colon_separator(line: bytes) -> bool:
return b':' in line Try / catch
from aiohttp.http_exceptions import InvalidHeader, BadHttpMessage
try:
await parser.feed_data(raw)
except (InvalidHeader, BadHttpMessage) as e:
# close the malformed connection, log the peer
... Prevention
- When building HTTP by hand, always use 'name: value' form.
- Don't feed non-HTTP byte streams into the HTTP parser.
When it happens
Trigger: A client or server sends a header line with no ':' separator, e.g. 'Host example.com' (space instead of colon), or a stray line fed to the parser.
Common situations: Hand-crafted HTTP via raw sockets, broken/upstream proxies injecting malformed lines, HTTP-smuggling probes, or non-HTTP data being fed into the parser.
Related errors
- Duplicate ' ' header found.
- Transfer-Encoding can't be present with Content-Length
- Bad HTTP method in status line
- Bad line ending, expected CRLF
- Bad status line
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/1fa9ec9c82669274.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/http_parser.py:181
def __init__(self, max_field_size: int = 8190, lax: bool = False) -> None:
self.max_field_size = max_field_size
self._lax = lax
def parse_headers(self, lines: list[bytes]) -> tuple[HeadersDictProxy, RawHeaders]:
headers: CIMultiDict[str] = CIMultiDict()
# note: "raw" does not mean inclusion of OWS before/after the field value
raw_headers = []
lines_idx = 0
line = lines[lines_idx]
line_count = len(lines)
while line:
# Parse initial header name : value pair.
try:
bname, bvalue = line.split(b":", 1)
except ValueError:
raise InvalidHeader(line) from None
if len(bname) == 0:
raise InvalidHeader(bname)
# https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2
if {bname[0], bname[-1]} & {32, 9}: # {" ", "\t"}
raise InvalidHeader(line)
bvalue = bvalue.lstrip(b" \t")
name = bname.decode("utf-8", "surrogateescape")
if not TOKENRE.fullmatch(name):
raise InvalidHeader(bname)
# next line
lines_idx += 1
line = lines[lines_idx]
# consume continuation linesView on GitHub (pinned to d041d4d0fd)