{"id":"691846c3489052e5","repo":"aio-libs/aiohttp","slug":"data-after-connection-close","errorCode":null,"errorMessage":"Data after `Connection: close`","messagePattern":"Data after `Connection: close`","errorType":"http","errorClass":"BadHttpMessage","httpStatus":400,"severity":"error","filePath":"aiohttp/http_parser.py","lineNumber":370,"sourceCode":"            if self._payload_parser is None and not self._upgraded:\n                if (\n                    self._max_msg_queue_size\n                    and self._msg_in_flight >= self._max_msg_queue_size\n                ):\n                    # Queue full: buffer the rest and stop. Safe pause point;\n                    # any preceding body is consumed before the next request\n                    # line. Resumes via feed_data(b\"\") when the queue drains.\n                    self._tail = data[start_pos:]\n                    break\n                pos = data.find(SEP, start_pos)\n                # consume \\r\\n\n                if pos == start_pos and not self._lines:\n                    start_pos = pos + len(SEP)\n                    continue\n\n                if pos >= start_pos:\n                    if should_close:\n                        raise BadHttpMessage(\"Data after `Connection: close`\")\n\n                    # line found\n                    line = data[start_pos:pos]\n                    if SEP == b\"\\n\":  # For lax response parsing\n                        line = line.rstrip(b\"\\r\")\n                    if len(line) > max_line_length:\n                        raise LineTooLong(line[:100] + b\"...\", max_line_length)\n\n                    self._lines.append(line)\n                    # After processing the status/request line, everything is a header.\n                    max_line_length = self.max_field_size\n\n                    if len(self._lines) > self.max_headers:\n                        raise BadHttpMessage(\"Too many headers received\")\n\n                    start_pos = pos + len(SEP)\n\n                    # \\r\\n\\r\\n found","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/http_parser.py#L352-L388","documentation":"Raised by HttpParser.feed_data (aiohttp/http_parser.py:370) after a message with should_close=True was fully parsed and additional bytes arrive on the same connection before EOF. should_close is set by Connection: close, HTTP/1.0 defaults, or error-status heuristics. Extra bytes after a close-signaled message violate connection semantics (pipelining after close) and are rejected to avoid mis-attributing them to the closed message.","triggerScenarios":"A peer sends a request/response with Connection: close (or HTTP/1.0) and then immediately pipelines another request on the same socket before it closes. Or a client reuses a socket the server already decided to close. The check fires on the NEXT line found after should_close was set.","commonSituations":"Keep-alive misconfiguration, clients that reuse a closing socket (common when a proxy/load-balancer decides to close mid-pipeline), or race conditions where pipelined requests overtake the close decision.","solutions":["Do not pipeline requests on a connection the peer signaled to close; open a new connection instead.","On the server side, let aiohttp close the socket and do not attempt to read more from it.","If you see this as a client, the server closed earlier than expected - retry the request on a fresh ClientSession/connection.","Review Connection: close semantics in any proxy between client and server."],"exampleFix":"# before - reusing a session the server is closing\nasync with session.get(url, headers={'Connection': 'close'}) as r1:\n    ...\nasync with session.get(url) as r2:   # same pool, server closing -> error\n    ...\n# after - open a fresh connection\nasync with aiohttp.ClientSession() as s2:\n    async with s2.get(url) as r2:\n        ...","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from aiohttp import http_exceptions\nfor attempt in range(3):\n    try:\n        async with session.post(url, data=payload) as r:\n            return await r.read()\n    except http_exceptions.BadHttpMessage as e:\n        if 'Connection: close' in str(e) and attempt < 2:\n            await asyncio.sleep(0.1 * (attempt + 1))\n            continue\n        raise","preventionTips":["Respect Connection: close - never reuse or pipeline on the socket","Retry idempotent requests on a fresh connection if a close arrives early","Disable client-side pipelining when the peer signals close"],"tags":["http","connection","keep-alive","parser","pipelining"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}