aio-libs/aiohttp · error · BadHttpMessage
Data after `Connection: close`
Error message
Data after `Connection: close`
What it means
Raised mid-parse when should_close is set (the peer signalled Connection: close, or another close condition) and additional non-empty lines arrive on the same connection. After a close-framed message the connection must carry nothing more; trailing bytes indicate pipeline injection or a peer that did not honor the close.
Solutions
- Close the socket immediately after sending/receiving Connection: close.
- Do not pipeline further requests after signalling close.
- Upgrade the client to honor connection lifecycle semantics.
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
# cannot validate remote bytes; instead, ensure your own client closes after Connection: close import socket sock.sendall(req_with_connection_close) # read the single response, then: sock.shutdown(socket.SHUT_WR) sock.close()
Type guard
null
Try / catch
from aiohttp.http_exceptions import BadHttpMessage
try:
await parser.feed_data(chunk)
except BadHttpMessage as e:
# 'Data after Connection: close' -> terminate the transport
transport.close() Prevention
- Treat Connection: close as terminal: read one message, then close.
- Never pipeline after sending close.
When it happens
Trigger: A peer sends Connection: close (or triggers should_close) and then keeps writing request lines or data on the same socket.
Common situations: HTTP keep-alive confusion, clients pipelining after a close signal, half-closed socket misuse, broken clients that ignore Connection: close.
Related errors
- Bad line ending, expected CRLF
- Bad HTTP method in status line
- Bad HTTP method in status line
- Bad status line
- Duplicate ' ' header found.
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/691846c3489052e5.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/http_parser.py:372
self._max_msg_queue_size
and self._msg_in_flight >= self._max_msg_queue_size
):
# Queue full: buffer the rest and stop. Safe pause point;
# any preceding body is consumed before the next request
# line. Resumes via feed_data(b"") when the queue drains.
self._tail = data[start_pos:]
# The remainder now lives in self._tail only. Don't return it.
data = EMPTY
break
pos = data.find(SEP, start_pos)
# consume \r\n
if pos == start_pos and not self._lines:
start_pos = pos + len(SEP)
continue
if pos >= start_pos:
if should_close:
raise BadHttpMessage("Data after `Connection: close`")
# line found
line = data[start_pos:pos]
if SEP == b"\n": # For lax response parsing
line = line.rstrip(b"\r")
if len(line) > max_line_length:
raise LineTooLong(line[:100] + b"...", max_line_length)
self._lines.append(line)
# After processing the status/request line, everything is a header.
max_line_length = self.max_field_size
if len(self._lines) > self.max_headers:
raise BadHttpMessage("Too many headers received")
start_pos = pos + len(SEP)
# \r\n\r\n foundView on GitHub (pinned to d041d4d0fd)