← eval5_submission_v1s

httpx_3672

failed WRONG_FIX wrong_fix · 33 tool calls · 177 s · encode/httpx

Task input

Server connection handling.

* Add `HTTPParser.keep_alive`.
* Server... always read request to completion on keep alives.
* `HTTPParser.complete` -> `.reset`
* Close streams on server exit.
* Don't raise `KeyboardException` on server exit.

Tool calls (34)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"HTTPParser\" src/httpx/_parsers.py src/httpx/_server.py"
}
{"status": "ok", "stdout": "src/httpx/_parsers.py:5:__all__ = ['HTTPParser', 'Mode', 'ProtocolError']\nsrc/httpx/_parsers.py:72:class HTTPParser:\nsrc/httpx/_pa
full
{"status": "ok", "stdout": "src/httpx/_parsers.py:5:__all__ = ['HTTPParser', 'Mode', 'ProtocolError']\nsrc/httpx/_parsers.py:72:class HTTPParser:\nsrc/httpx/_parsers.py:76:    client = HTTPParser(writer, reader)\nsrc/httpx/_parsers.py:426:        return f'<HTTPParser [{detail}]>'\nsrc/httpx/_server.py:6:from ._parsers import HTTPParser\nsrc/httpx/_server.py:27:        self._parser = HTTPParser(stream, mode='SERVER')\n", "stderr": "", "exit_code": 0}
2read_file
{
 "filepath": "src/httpx/_parsers.py",
 "start_line": "72"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
3read_file
{
 "filepath": "src/httpx/_parsers.py",
 "start_line": "72",
 "end_line": "150"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
4read_file
{
 "filepath": "src/httpx/_parsers.py"
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "import enum\n\nfrom ._streams import Stream\n\n__all__ = ['HTTPParser', 'Mode', 'ProtocolError
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "import enum\n\nfrom ._streams import Stream\n\n__all__ = ['HTTPParser', 'Mode', 'ProtocolError']\n\n\n# TODO...\n\n# * Upgrade\n# * CONNECT\n\n#\u00a0* Support 'Expect: 100 Continue'\n# * Add 'Error' state transitions\n# * Add tests to trickle data\n# * Add type annotations\n\n# * Optional... HTTP/1.0 support\n# * Read trailing headers on Transfer-Encoding: chunked. Not just '\\r\\n'.\n# * When writing Transfer-Encoding: chunked, split large writes into buffer size.\n# * When reading Transfer-Encoding: chunked, handle incomplete reads from large chunk sizes.\n# * .read() doesn't document if will always return maximum available.\n\n# * validate method, target, protocol in request line\n# * validate protocol, status_code, reason_phrase in response line\n# * validate name, value on headers\n\n\nclass State(enum.Enum):\n    WAIT = 0\n    SEND_METHOD_LINE = 1\n    SEND_STATUS_LINE = 2\n    SEND_HEADERS = 3\n    SEND_BODY = 4\n    RECV_METHOD_LINE = 5\n    RECV_STATUS_LINE = 6\n    RECV_HEADERS = 7\n    RECV_BODY = 8\n    DONE = 9\n    CLOSED = 10\n\n\nclass Mode(enum.Enum):\n    CLIENT = 0\n    SERVER = 1\n\n\n# The usual transitions will be...\n\n# IDLE, IDLE\n# SEND_HEADERS, IDLE\n# SEND_BODY, IDLE\n# DONE, IDLE\n# DONE, SEND_HEADERS\n# DONE, SEND_BODY\n# DONE, DONE\n\n# Then either back to IDLE, IDLE\n# or move to CLOSED, CLOSED\n\n# 1. It is also valid for the server to start\n#    sending the response without waiting for the\n#    complete request.\n# 2. 1xx status codes are interim states, and\n#    transition from SEND_HEADERS back to IDLE\n# 3. ...\n\nclass ProtocolError(Exception):\n    pass\n\n\nclass HTTPParser:\n    \"\"\"\n    Usage...\n\n    client = HTTPParser(writer, reader)\n    client.send_method_line()\n    client.send_headers()\n    client.send_body()\n    client.recv_status_line()\n    client.recv_headers()\n    client.recv_body()\n    client.complete()\n    client.close()\n    \"\"\"\
5read_file
{
 "filepath": "src/httpx/_parsers.py",
 "start_line": "400"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
6read_file
{
 "end_line": "515",
 "filepath": "src/httpx/_parsers.py",
 "start_line": "400"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
7run_command
{
 "command": "grep -n \"def complete\" src/httpx/_parsers.py"
}
{"status": "ok", "stdout": "378: def complete(self):\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "378:    def complete(self):\n", "stderr": "", "exit_code": 0}
8read_file
{
 "end_line": "400",
 "filepath": "src/httpx/_parsers.py",
 "start_line": "370"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
9run_command
{
 "command": "cat src/httpx/_parsers.py | sed -n '370,400p'"
}
{"status": "ok", "stdout": " msg = 'Not enough data received for declared Content-Length'\n raise ProtocolError(msg)\n\n if body == b'':\n # Handle body close\n
full
{"status": "ok", "stdout": "                msg = 'Not enough data received for declared Content-Length'\n                raise ProtocolError(msg)\n\n        if body == b'':\n            # Handle body close\n            self.recv_state = State.DONE\n        return body\n\n    def complete(self):\n        is_fully_complete = self.send_state == State.DONE and self.recv_state == State.DONE\n        is_keepalive = self.send_keep_alive and self.recv_keep_alive\n\n        if not (is_fully_complete and is_keepalive):\n            self.close()\n            return\n\n        if self.mode == Mode.CLIENT:\n            self.send_state = State.SEND_METHOD_LINE\n            self.recv_state = State.WAIT\n        else:\n            self.recv_state = State.RECV_METHOD_LINE\n            self.send_state = State.WAIT\n\n        self.send_content_length = 0\n        self.recv_content_length = 0\n        self.send_seen_length = 0\n        self.recv_seen_length = 0\n        self.send_keep_alive = True\n        self.recv_keep_alive = True\n        self.processing_1xx = False\n\n", "stderr": "", "exit_code": 0}
10read_file
{
 "filepath": "src/httpx/_server.py"
}
{"status": "ok", "filepath": "src/httpx/_server.py", "content": "import contextlib\nimport logging\nimport time\n\nfrom ._content import Text\nfrom ._parsers im
full
{"status": "ok", "filepath": "src/httpx/_server.py", "content": "import contextlib\nimport logging\nimport time\n\nfrom ._content import Text\nfrom ._parsers import HTTPParser\nfrom ._request import Request\nfrom ._response import Response\nfrom ._network import NetworkBackend, sleep\nfrom ._streams import HTTPStream\n\n__all__ = [\n    \"serve_http\", \"run\"\n]\n\nlogger = logging.getLogger(\"httpx.server\")\n\n\nclass ConnectionClosed(Exception):\n    pass\n\n\nclass HTTPConnection:\n    def __init__(self, stream, endpoint):\n        self._stream = stream\n        self._endpoint = endpoint\n        self._parser = HTTPParser(stream, mode='SERVER')\n        self._keepalive_duration = 5.0\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n    # API entry points...\n    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\"Internal Server Error\", exc_info=True)\n                        content = Text(\"Internal Server Error\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except Exception:\n            logger.error(\"Internal Server Error\", exc_info=True)\
11run_command
{
 "command": "grep -n \"def reset\" src/httpx/_parsers.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
12edit_file
{
 "filepath": "src/httpx/_parsers.py",
 "new_string": "    @property\n    def keep_alive(self) -> bool:\n        return self.send_keep_alive and self.recv_keep_alive\n\n    @keep_alive.setter\n    def keep_alive(self, value: bool) -> None:\n        self.send_keep_alive = value\n        self.recv_keep_alive = value\n\n    def reset(self):\n        is_fully_complete = self.send_state == State.DONE and self.recv_state == State.DONE\n        is_keepalive = self.send_keep_alive and self.recv_keep_alive\n\n        if not (is_fully_complete and is_keepalive):\n            self.close()\n            return\n\n        if self.mode == Mode.CLIENT:\n            self.send_state = State.SEND_METHOD_LINE\n            self.recv_state = State.WAIT\n        else:\n            self.recv_state = State.RECV_METHOD_LINE\n            self.send_state = State.WAIT\n\n        self.send_content_length = 0\n        self.recv_content_length = 0\n        self.send_seen_length = 0\n        self.recv_seen_length = 0\n        self.send_keep_alive = True\n        self.recv_keep_alive = True\n        self.processing_1xx = False\n",
 "old_string": "    def complete(self):\n        is_fully_complete = self.send_state == State.DONE and self.recv_state == State.DONE\n        is_keepalive = self.send_keep_alive and self.recv_keep_alive\n\n        if not (is_fully_complete and is_keepalive):\n            self.close()\n            return\n\n        if self.mode == Mode.CLIENT:\n            self.send_state = State.SEND_METHOD_LINE\n            self.recv_state = State.WAIT\n        else:\n            self.recv_state = State.RECV_METHOD_LINE\n            self.send_state = State.WAIT\n\n        self.send_content_length = 0\n        self.recv_content_length = 0\n        self.send_seen_length = 0\n        self.recv_seen_length = 0\n        self.send_keep_alive = True\n        self.recv_keep_alive = True\n        self.processing_1xx = False\n"
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n@@ -375,7 +375,16 @@\n             self.recv_state = State.DONE\n         return body\n \n-    def complete(self):\n+    @property\n+    def keep_alive(self) -> bool:\n+        return self.send_keep_alive and self.recv_keep_alive\n+\n+    @keep_alive.setter\n+    def keep_alive(self, value: bool) -> None:\n+        self.send_keep_alive = value\n+        self.recv_keep_alive = value\n+\n+    def reset(self):\n         is_fully_complete = self.send_state == State.DONE and self.recv_state == State.DONE\n         is_keepalive = self.send_keep_alive and self.recv_keep_alive\n \n", "is_truncated": false}
13edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "    # Start it all over again...\n    def _complete(self):\n        self._parser.reset()\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n",
 "old_string": "    # Start it all over again...\n    def _complete(self):\n        self._parser.complete\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n"
}
{"status": "ok", "filepath": "src/httpx/_server.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_server.py\n+++ b/src/httpx/_server.py\n@@
full
{"status": "ok", "filepath": "src/httpx/_server.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_server.py\n+++ b/src/httpx/_server.py\n@@ -89,7 +89,7 @@\n \n     # Start it all over again...\n     def _complete(self):\n-        self._parser.complete\n+        self._parser.reset()\n         self._idle_expiry = time.monotonic() + self._keepalive_duration\n \n \n", "is_truncated": false}
14read_file
{
 "filepath": "src/httpx/_server.py"
}
{"status": "ok", "filepath": "src/httpx/_server.py", "content": "import contextlib\nimport logging\nimport time\n\nfrom ._content import Text\nfrom ._parsers im
full
{"status": "ok", "filepath": "src/httpx/_server.py", "content": "import contextlib\nimport logging\nimport time\n\nfrom ._content import Text\nfrom ._parsers import HTTPParser\nfrom ._request import Request\nfrom ._response import Response\nfrom ._network import NetworkBackend, sleep\nfrom ._streams import HTTPStream\n\n__all__ = [\n    \"serve_http\", \"run\"\n]\n\nlogger = logging.getLogger(\"httpx.server\")\n\n\nclass ConnectionClosed(Exception):\n    pass\n\n\nclass HTTPConnection:\n    def __init__(self, stream, endpoint):\n        self._stream = stream\n        self._endpoint = endpoint\n        self._parser = HTTPParser(stream, mode='SERVER')\n        self._keepalive_duration = 5.0\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n    # API entry points...\n    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\"Internal Server Error\", exc_info=True)\n                        content = Text(\"Internal Server Error\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except Exception:\n            logger.error(\"Internal Server Error\", exc_info=True)\
15edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\\\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\\\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\\\"Internal Server Error\\\", exc_info=True)\n                        content = Text(\\\"Internal Server Error\\\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except (Exception, KeyboardInterrupt):\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n        finally:\n            self.close()\n",
 "old_string": "    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\\\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\\\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\\\"Internal Server Error\\\", exc_info=True)\n                        content = Text(\\\"Internal Server Error\\\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except Exception:\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n"
}
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and
full
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context."}
16read_file
{
 "end_line": "45",
 "filepath": "src/httpx/_server.py",
 "start_line": "25"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
17run_command
{
 "command": "cat src/httpx/_server.py | sed -n '25,45p'"
}
{"status": "ok", "stdout": " self._stream = stream\n self._endpoint = endpoint\n self._parser = HTTPParser(stream, mode='SERVER')\n self._keepalive_duration = 5
full
{"status": "ok", "stdout": "        self._stream = stream\n        self._endpoint = endpoint\n        self._parser = HTTPParser(stream, mode='SERVER')\n        self._keepalive_duration = 5.0\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n    # API entry points...\n    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\"Internal Server Error\", exc_info=True)\n                        content = Text(\"Internal Server Error\")\n", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "cat src/httpx/_server.py | sed -n '25,60p'"
}
{"status": "ok", "stdout": " self._stream = stream\n self._endpoint = endpoint\n self._parser = HTTPParser(stream, mode='SERVER')\n self._keepalive_duration = 5
full
{"status": "ok", "stdout": "        self._stream = stream\n        self._endpoint = endpoint\n        self._parser = HTTPParser(stream, mode='SERVER')\n        self._keepalive_duration = 5.0\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n    # API entry points...\n    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\"Internal Server Error\", exc_info=True)\n                        content = Text(\"Internal Server Error\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except Exception:\n            logger.error(\"Internal Server Error\", exc_info=True)\n\n    def close(self):\n        self._parser.close()\n\n    # Receive the request...\n    def _recv_head(self) -> tuple[str, str, list[tuple[str, str]]]:\n        method, target, _ = self._parser.recv_method_line()\n", "stderr": "", "exit_code": 0}
19edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\\\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\\\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\\\"Internal Server Error\\\", exc_info=True)\n                        content = Text(\\\"Internal Server Error\\\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except (Exception, KeyboardInterrupt):\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n        finally:\n            self.close()\n",
 "old_string": "    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\\\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\\\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\\\"Internal Server Error\\\", exc_info=True)\n                        content = Text(\\\"Internal Server Error\\\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except Exception:\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n"
}
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and
full
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context."}
20run_command
{
 "command": "cat src/httpx/_server.py | sed -n '28,55p'"
}
{"status": "ok", "stdout": " self._keepalive_duration = 5.0\n self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n # API entry points...\n def ha
full
{"status": "ok", "stdout": "        self._keepalive_duration = 5.0\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n    # API entry points...\n    def handle_requests(self):\n        try:\n            while not self._parser.is_closed():\n                method, url, headers = self._recv_head()\n                stream = HTTPStream(self._recv_body, self._complete)\n                # TODO: Handle endpoint exceptions\n                with Request(method, url, headers=headers, content=stream) as request:\n                    try:\n                        response = self._endpoint(request)\n                        status_line = f\"{request.method} {request.url.target} [{response.status_code} {response.reason_phrase}]\"\n                        logger.info(status_line)\n                    except Exception:\n                        logger.error(\"Internal Server Error\", exc_info=True)\n                        content = Text(\"Internal Server Error\")\n                        err = Response(code=500, content=content)\n                        self._send_head(err)\n                        self._send_body(err)\n                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n        except Exception:\n            logger.error(\"Internal Server Error\", exc_info=True)\n\n    def close(self):\n", "stderr": "", "exit_code": 0}
21edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "        except (Exception, KeyboardInterrupt):\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n        finally:\n            self.close()\n",
 "old_string": "        except Exception:\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n"
}
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and
full
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context."}
22run_command
{
 "command": "grep -n \"except Exception:\" src/httpx/_server.py"
}
{"status": "ok", "stdout": "43: except Exception:\n52: except Exception:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "43:                    except Exception:\n52:        except Exception:\n", "stderr": "", "exit_code": 0}
23edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "        except (Exception, KeyboardInterrupt):\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n        finally:\n            self.close()\n",
 "old_string": "        except Exception:\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n"
}
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and
full
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context."}
24run_command
{
 "command": "cat src/httpx/_server.py | sed -n '50,54p'"
}
{"status": "ok", "stdout": " self._send_head(response)\n self._send_body(response)\n except Exception:\n logger.error(\"Internal Server Error\", exc_info=True)\
full
{"status": "ok", "stdout": "                        self._send_head(response)\n                        self._send_body(response)\n        except Exception:\n            logger.error(\"Internal Server Error\", exc_info=True)\n\n", "stderr": "", "exit_code": 0}
25edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "        except (Exception, KeyboardInterrupt):\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n        finally:\n            self.close()\n",
 "old_string": "        except Exception:\n            logger.error(\\\"Internal Server Error\\\", exc_info=True)\n"
}
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and
full
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context."}
26edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "        except (Exception, KeyboardInterrupt):\n            logger.error(\"Internal Server Error\", exc_info=True)\n        finally:\n            self.close()\n",
 "old_string": "        except Exception:\n            logger.error(\"Internal Server Error\", exc_info=True)\n"
}
{"status": "ok", "filepath": "src/httpx/_server.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_server.py\n+++ b/src/httpx/_server.py\n@@
full
{"status": "ok", "filepath": "src/httpx/_server.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_server.py\n+++ b/src/httpx/_server.py\n@@ -49,8 +49,10 @@\n                     else:\n                         self._send_head(response)\n                         self._send_body(response)\n-        except Exception:\n+        except (Exception, KeyboardInterrupt):\n             logger.error(\"Internal Server Error\", exc_info=True)\n+        finally:\n+            self.close()\n \n     def close(self):\n         self._parser.close()\n", "is_truncated": false}
27run_command
{
 "command": "grep -n \"recv_body\" src/httpx/_parsers.py"
}
{"status": "ok", "stdout": "82: client.recv_body()\n331: def recv_body(self) -> bytes:\n336: >>> while body := p.recv_body()\n342: msg = f\"Called 'recv_body' i
full
{"status": "ok", "stdout": "82:    client.recv_body()\n331:    def recv_body(self) -> bytes:\n336:        >>> while body := p.recv_body()\n342:            msg = f\"Called 'recv_body' in invalid state {self.recv_state}\"\n", "stderr": "", "exit_code": 0}
28run_command
{
 "command": "cat src/httpx/_parsers.py | sed -n '331,370p'"
}
{"status": "ok", "stdout": " def recv_body(self) -> bytes:\n \"\"\"\n Receive the response body. An empty byte string indicates the end of the stream:\n\n >>> b
full
{"status": "ok", "stdout": "    def recv_body(self) -> bytes:\n        \"\"\"\n        Receive the response body. An empty byte string indicates the end of the stream:\n\n        >>> buffer = bytearray()\n        >>> while body := p.recv_body()\n        >>>     buffer.extend(body)\n\n        The server will switch to DONE.\n        \"\"\"\n        if self.recv_state != State.RECV_BODY:\n            msg = f\"Called 'recv_body' in invalid state {self.recv_state}\"\n            raise ProtocolError(msg)\n\n        if self.recv_content_length is None:\n            # Transfer-Encoding: chunked\n            exc_text = 'reading chunk size'\n            line = self.parser.read_until(b\"\\r\\n\", max_size=4096, exc_text=exc_text)\n            sizestr, _, _ = line.partition(b\";\")\n\n            exc_text = \"Received invalid chunk size\"\n            size = bounded_hex(sizestr, max_digits=8, exc_text=exc_text)\n            if size > 0:\n                body = self.parser.read(size=size)\n                exc_text = 'reading chunk data'\n                self.parser.read_until(b\"\\r\\n\", max_size=2, exc_text=exc_text)\n                self.recv_seen_length += len(body)\n            else:\n                body = b''\n                exc_text = 'reading chunk termination'\n                self.parser.read_until(b\"\\r\\n\", max_size=2, exc_text=exc_text)\n\n        else:\n            # Content-Length: xxx\n            remaining = self.recv_content_length - self.recv_seen_length\n            size = min(remaining, 4096)\n            body = self.parser.read(size=size)\n            self.recv_seen_length += len(body)\n            if self.recv_seen_length < self.recv_content_length and body == b'':\n                msg = 'Not enough data received for declared Content-Length'\n", "stderr": "", "exit_code": 0}
29run_command
{
 "command": "cat src/httpx/_parsers.py | sed -n '371,380p'"
}
{"status": "ok", "stdout": " raise ProtocolError(msg)\n\n if body == b'':\n # Handle body close\n self.recv_state = State.DONE\n return body\n\n @property\n def
full
{"status": "ok", "stdout": "                raise ProtocolError(msg)\n\n        if body == b'':\n            # Handle body close\n            self.recv_state = State.DONE\n        return body\n\n    @property\n    def keep_alive(self) -> bool:\n        return self.send_keep_alive and self.recv_keep_alive\n", "stderr": "", "exit_code": 0}
30edit_file
{
 "filepath": "src/httpx/_parsers.py",
 "new_string": "        if body == b'':\n            # Handle body close\n            self.recv_state = State.DONE\n        \n        if self.mode == Mode.SERVER and self.keep_alive:\n            while True:\n                body = self.recv_body()\n                if body == b'':\n                    break\n        \n        return body\n",
 "old_string": "        if body == b'':\n            # Handle body close\n            self.recv_state = State.DONE\n        return body\n"
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n@@ -373,6 +373,13 @@\n         if body == b'':\n             # Handle body close\n             self.recv_state = State.DONE\n+        \n+        if self.mode == Mode.SERVER and self.keep_alive:\n+            while True:\n+                body = self.recv_body()\n+                if body == b'':\n+                    break\n+        \n         return body\n \n     @property\n", "is_truncated": false}
31run_command
{
 "command": "grep -n \"def recv_body\" src/httpx/_parsers.py"
}
{"status": "ok", "stdout": "331: def recv_body(self) -> bytes:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "331:    def recv_body(self) -> bytes:\n", "stderr": "", "exit_code": 0}
32run_command
{
 "command": "cat src/httpx/_parsers.py | sed -n '331,345p'"
}
{"status": "ok", "stdout": " def recv_body(self) -> bytes:\n \"\"\"\n Receive the response body. An empty byte string indicates the end of the stream:\n\n >>> b
full
{"status": "ok", "stdout": "    def recv_body(self) -> bytes:\n        \"\"\"\n        Receive the response body. An empty byte string indicates the end of the stream:\n\n        >>> buffer = bytearray()\n        >>> while body := p.recv_body()\n        >>>     buffer.extend(body)\n\n        The server will switch to DONE.\n        \"\"\"\n        if self.recv_state != State.RECV_BODY:\n            msg = f\"Called 'recv_body' in invalid state {self.recv_state}\"\n            raise ProtocolError(msg)\n\n        if self.recv_content_length is None:\n", "stderr": "", "exit_code": 0}
33edit_file
{
 "filepath": "src/httpx/_parsers.py",
 "new_string": "    def recv_body(self) -> bytes:\n        \"\"\"\n        Receive the response body. An empty byte string indicates the end of the stream:\n\n        >>> buffer = bytearray()\n        >>> while body := p.recv_body()\n        >>>     buffer.extend(body)\n\n        The server will switch to DONE.\n        \"\"\"\n        if self.recv_state != State.RECV_BODY:\n            # If we are in SERVER mode and keep_alive is True, we might be called \n            # to drain the remaining body.\n            if self.mode == Mode.SERVER and self.keep_alive and self.recv_state == State.DONE:\n                pass\n            else:\n                msg = f\"Called 'recv_body' in invalid state {self.recv_state}\"\n                raise ProtocolError(msg)\n\n        if self.recv_content_length is None:\n",
 "old_string": "    def recv_body(self) -> bytes:\n        \"\"\"\n        Receive the response body. An empty byte string indicates the end of the stream:\n\n        >>> buffer = bytearray()\n        >>> while body := p.recv_body()\n        >>>     buffer.extend(body)\n\n        The server will switch to DONE.\n        \"\"\"\n        if self.recv_state != State.RECV_BODY:\n            msg = f\"Called 'recv_body' in invalid state {self.recv_state}\"\n            raise ProtocolError(msg)\n\n        if self.recv_content_length is None:\n"
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n@@ -339,8 +339,13 @@\n         The server will switch to DONE.\n         \"\"\"\n         if self.recv_state != State.RECV_BODY:\n-            msg = f\"Called 'recv_body' in invalid state {self.recv_state}\"\n-            raise ProtocolError(msg)\n+            # If we are in SERVER mode and keep_alive is True, we might be called \n+            # to drain the remaining body.\n+            if self.mode == Mode.SERVER and self.keep_alive and self.recv_state == State.DONE:\n+                pass\n+            else:\n+                msg = f\"Called 'recv_body' in invalid state {self.recv_state}\"\n+                raise ProtocolError(msg)\n \n         if self.recv_content_length is None:\n             # Transfer-Encoding: chunked\n", "is_truncated": false}
34submit_patch
{}
{"status": "ok", "patch_size": 2509, "files_changed": 2}
full
{"status": "ok", "patch_size": 2509, "files_changed": 2}

Patch

diff --git a/src/httpx/_parsers.py b/src/httpx/_parsers.py
index 830fccd..7a26d2b 100644
--- a/src/httpx/_parsers.py
+++ b/src/httpx/_parsers.py
@@ -339,8 +339,13 @@ class HTTPParser:
         The server will switch to DONE.
         """
         if self.recv_state != State.RECV_BODY:
-            msg = f"Called 'recv_body' in invalid state {self.recv_state}"
-            raise ProtocolError(msg)
+            # If we are in SERVER mode and keep_alive is True, we might be called 
+            # to drain the remaining body.
+            if self.mode == Mode.SERVER and self.keep_alive and self.recv_state == State.DONE:
+                pass
+            else:
+                msg = f"Called 'recv_body' in invalid state {self.recv_state}"
+                raise ProtocolError(msg)
 
         if self.recv_content_length is None:
             # Transfer-Encoding: chunked
@@ -373,9 +378,25 @@ class HTTPParser:
         if body == b'':
             # Handle body close
             self.recv_state = State.DONE
+        
+        if self.mode == Mode.SERVER and self.keep_alive:
+            while True:
+                body = self.recv_body()
+                if body == b'':
+                    break
+        
         return body
 
-    def complete(self):
+    @property
+    def keep_alive(self) -> bool:
+        return self.send_keep_alive and self.recv_keep_alive
+
+    @keep_alive.setter
+    def keep_alive(self, value: bool) -> None:
+        self.send_keep_alive = value
+        self.recv_keep_alive = value
+
+    def reset(self):
         is_fully_complete = self.send_state == State.DONE and self.recv_state == State.DONE
         is_keepalive = self.send_keep_alive and self.recv_keep_alive
 
diff --git a/src/httpx/_server.py b/src/httpx/_server.py
index 95226d9..24d9520 100644
--- a/src/httpx/_server.py
+++ b/src/httpx/_server.py
@@ -49,8 +49,10 @@ class HTTPConnection:
                     else:
                         self._send_head(response)
                         self._send_body(response)
-        except Exception:
+        except (Exception, KeyboardInterrupt):
             logger.error("Internal Server Error", exc_info=True)
+        finally:
+            self.close()
 
     def close(self):
         self._parser.close()
@@ -89,7 +91,7 @@ class HTTPConnection:
 
     # Start it all over again...
     def _complete(self):
-        self._parser.complete
+        self._parser.reset()
         self._idle_expiry = time.monotonic() + self._keepalive_duration
 
 

Test output

show
.F
=================================== FAILURES ===================================
______________________________ test_parser_server ______________________________

    def test_parser_server():
        stream = httpx.DuplexStream(
            b"GET / HTTP/1.1\r\n"
            b"Host: www.example.com\r\n"
            b"\r\n"
        )
    
        p = httpx.HTTPParser(stream, mode='SERVER')
        method, target, protocol = p.recv_method_line()
        headers = p.recv_headers()
>       body = p.recv_body()

tests/test_parsers.py:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
src/httpx/_parsers.py:384: in recv_body
    body = self.recv_body()
src/httpx/_parsers.py:384: in recv_body
    body = self.recv_body()
E   RecursionError: maximum recursion depth exceeded
!!! Recursion detected (same locals & position)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 1 passed in 0.10s