← eval4_submission_v1b

httpx_3672

resolved RESOLVED PASS · 13 tool calls · 136 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 (14)

#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", "exit_code": 0}
2read_file
{
 "filepath": "src/httpx/_parsers.py",
 "start_line": 72,
 "end_line": 150
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "class HTTPParser:\n \"\"\"\n Usage...\n\n client = HTTPParser(writer, reader)\n client.send_me
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "class 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    \"\"\"\n    def __init__(self, stream: Stream, mode: str) -> None:\n        self.stream = stream\n        self.parser = ReadAheadParser(stream)\n        self.mode = {'CLIENT': Mode.CLIENT, 'SERVER': Mode.SERVER}[mode]\n\n        # Track state...\n        if self.mode == Mode.CLIENT:\n            self.send_state: State = State.SEND_METHOD_LINE\n            self.recv_state: State = State.WAIT\n        else:\n            self.recv_state = State.RECV_METHOD_LINE\n            self.send_state = State.WAIT\n\n        # Track message framing...\n        self.send_content_length: int | None = 0\n        self.recv_content_length: int | None = 0\n        self.send_seen_length = 0\n        self.recv_seen_length = 0\n\n        # Track connection keep alive...\n        self.send_keep_alive = True\n        self.recv_keep_alive = True\n\n        # Special states...\n        self.processing_1xx = False\n\n    def send_method_line(self, method: bytes, target: bytes, protocol: bytes) -> None:\n        \"\"\"\n        Send the initial request line:\n\n        >>> p.send_method_line(b'GET', b'/', b'HTTP/1.1')\n\n        Sending state will switch to SEND_HEADERS state.\n        \"\"\"\n        if self.send_state != State.SEND_METHOD_LINE:\n            msg = f\"Called 'send_method_line' in invalid state {self.send_state}\"\n            raise ProtocolError(msg)\n\n        # Send initial request line, eg. \"GET / HTTP/1.1\"\n        if protocol != b'HTTP/1.1':\n            raise ProtocolError(\"Sent unsupported protocol version\")\n        data = b\" \".join([method, target, protocol]) + b\"\\r\\n\"\n        self.stream.write(data)\
3read_file
{
 "end_line": 515,
 "filepath": "src/httpx/_parsers.py",
 "start_line": 400
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "\n def close(self):\n if self.send_state != State.CLOSED:\n self.send_state = State.CLOSED\n s
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "\n    def close(self):\n        if self.send_state != State.CLOSED:\n            self.send_state = State.CLOSED\n            self.recv_state = State.CLOSED\n            self.stream.close()\n\n    def is_idle(self) -> bool:\n        return (\n            self.send_state == State.SEND_METHOD_LINE or\n            self.recv_state == State.RECV_METHOD_LINE\n        )\n\n    def is_closed(self) -> bool:\n        return self.send_state == State.CLOSED\n\n    def description(self) -> str:\n        return {\n            State.SEND_METHOD_LINE: \"idle\",\n            State.CLOSED: \"closed\",\n        }.get(self.send_state, \"active\")\n\n    def __repr__(self) -> str:\n        cl_state = self.send_state.name\n        sr_state = self.recv_state.name\n        detail = f\"client {cl_state}, server {sr_state}\"\n        return f'<HTTPParser [{detail}]>'\n\n\nclass ReadAheadParser:\n    \"\"\"\n    A buffered I/O stream, with methods for read-ahead parsing.\n    \"\"\"\n    def __init__(self, stream: Stream) -> None:\n        self._buffer = b''\n        self._stream = stream\n        self._chunk_size = 4096\n\n    def _read_some(self) -> bytes:\n        if self._buffer:\n            ret, self._buffer = self._buffer, b''\n            return ret\n        return self._stream.read(self._chunk_size)\n\n    def _push_back(self, buffer):\n        assert self._buffer == b''\n        self._buffer = buffer\n\n    def read(self, size: int) -> bytes:\n        \"\"\"\n        Read and return up to 'size' bytes from the stream, with I/O buffering provided.\n\n        * Returns b'' to indicate connection close.\n        \"\"\"\n        buffer = bytearray()\n        while len(buffer) < size:\n            chunk = self._read_some()\n            if not chunk:\n                break\n            buffer.extend(chunk)\n\n        if len(buffer) > size:\n            buffer, push_back = buffer[:size], buffer[size:]\n            self._push_
4read_file
{
 "end_line": 400,
 "filepath": "src/httpx/_parsers.py",
 "start_line": 200
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": " \"\"\"\n if self.send_state != State.SEND_BODY:\n msg = f\"Called 'send_body' in invalid stat
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "        \"\"\"\n        if self.send_state != State.SEND_BODY:\n            msg = f\"Called 'send_body' in invalid state {self.send_state}\"\n            raise ProtocolError(msg)\n\n        if self.send_content_length is None:\n            # Transfer-Encoding: chunked\n            self.send_seen_length += len(body)\n            marker = f'{len(body):x}\\r\\n'.encode('ascii')\n            self.stream.write(marker + body + b'\\r\\n')\n\n        else:\n            # Content-Length: xxx\n            self.send_seen_length += len(body)\n            if self.send_seen_length > self.send_content_length:\n                msg = 'Too much data sent for declared Content-Length'\n                raise ProtocolError(msg)\n            if self.send_seen_length < self.send_content_length and body == b'':\n                msg = 'Not enough data sent for declared Content-Length'\n                raise ProtocolError(msg)\n            if body:\n                self.stream.write(body)\n\n        if body == b'':\n            # Handle body close\n            self.send_state = State.DONE\n\n    def recv_method_line(self) -> tuple[bytes, bytes, bytes]:\n        \"\"\"\n        Receive the initial request method line:\n\n        >>> method, target, protocol = p.recv_status_line()\n\n        Receive state will switch to RECV_HEADERS.\n        \"\"\"\n        if self.recv_state != State.RECV_METHOD_LINE:\n            msg = f\"Called 'recv_method_line' in invalid state {self.recv_state}\"\n            raise ProtocolError(msg)\n\n        # Read initial response line, eg. \"GET / HTTP/1.1\"\n        exc_text = \"reading request method line\"\n        line = self.parser.read_until(b\"\\r\\n\", max_size=4096, exc_text=exc_text)\n        method, target, protocol = line.split(b\" \", 2)\n        if protocol != b'HTTP/1.1':\n            raise ProtocolError(\"Received unsupported protocol version\")\n\n        self.recv_state = State.RECV_
5read_file
{
 "end_line": 400,
 "filepath": "src/httpx/_parsers.py",
 "start_line": 350
}
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "\n exc_text = \"Received invalid chunk size\"\n size = bounded_hex(sizestr, max_digits=8, exc_
full
{"status": "ok", "filepath": "src/httpx/_parsers.py", "content": "\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                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   
6read_file
{
 "end_line": 100,
 "filepath": "src/httpx/_server.py",
 "start_line": 1
}
{"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)\
7read_file
{
 "end_line": 126,
 "filepath": "src/httpx/_server.py",
 "start_line": 101
}
{"status": "ok", "filepath": "src/httpx/_server.py", "content": " while(True):\n sleep(1)\n\n\n@contextlib.contextmanager\ndef serve_http(endpoint):\n def handl
full
{"status": "ok", "filepath": "src/httpx/_server.py", "content": "        while(True):\n            sleep(1)\n\n\n@contextlib.contextmanager\ndef serve_http(endpoint):\n    def handler(stream):\n        connection = HTTPConnection(stream, endpoint)\n        connection.handle_requests()\n\n    logging.basicConfig(\n        format=\"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n        datefmt=\"%Y-%m-%d %H:%M:%S\",\n        level=logging.DEBUG\n    )\n\n    backend = NetworkBackend()\n    with backend.serve(\"127.0.0.1\", 8080, handler) as server:\n        server = HTTPServer(server.host, server.port)\n        logger.info(f\"Serving on {server.url} (Press CTRL+C to quit)\")\n        yield server\n\n\ndef run(app):\n    with serve_http(app) as server:\n        server.wait()\n", "start_line": 101, "end_line": 126, "total_lines": 126, "is_truncated": false}
8edit_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 send_method_line(self, method: bytes, target: bytes, protocol: bytes) -> None:",
 "old_string": "    def send_method_line(self, method: bytes, target: bytes, protocol: bytes) -> None:"
}
{"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@@ -108,6 +108,15 @@\n \n         # Special states...\n         self.processing_1xx = False\n+\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 send_method_line(self, method: bytes, target: bytes, protocol: bytes) -> None:\n         \"\"\"\n", "is_truncated": false}
9edit_file
{
 "filepath": "src/httpx/_parsers.py",
 "new_string": "    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",
 "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"
}
{"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@@ -384,7 +384,7 @@\n             self.recv_state = State.DONE\n         return body\n \n-    def complete(self):\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}
10read_file
{
 "end_line": 100,
 "filepath": "src/httpx/_server.py",
 "start_line": 30
}
{"status": "ok", "filepath": "src/httpx/_server.py", "content": "\n # API entry points...\n def handle_requests(self):\n try:\n while not self._parser.is_closed
full
{"status": "ok", "filepath": "src/httpx/_server.py", "content": "\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        m = method.decode('ascii')\n        t = target.decode('ascii')\n        headers = self._parser.recv_headers()\n        h = [\n            (k.decode('latin-1'), v.decode('latin-1'))\n            for k, v in headers\n        ]\n        return m, t, h\n\n    def _recv_body(self):\n        return self._parser.recv_body()\n\n    # Return the response...\n    def _send_head(self, response: Response):\n        protocol = b\"HTTP/1.1\"\n        status = response.status_code\n        re
11edit_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",
 "old_string": "    # Start it all over again...\n    def _complete(self):\n        self._parser.complete\n        self._idle_expiry = time.monotonic() + self._keepalive_duration"
}
{"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}
12edit_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                \n                if self._parser.keep_alive:\n                    # Read request to completion on keep alives\n                    while not self._parser.is_idle():\n                        self._parser.recv_body()\n        except Exception:\n            logger.error(\"Internal Server Error\", exc_info=True)",
 "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)"
}
{"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,6 +49,11 @@\n                     else:\n                         self._send_head(response)\n                         self._send_body(response)\n+                \n+                if self._parser.keep_alive:\n+                    # Read request to completion on keep alives\n+                    while not self._parser.is_idle():\n+                        self._parser.recv_body()\n         except Exception:\n             logger.error(\"Internal Server Error\", exc_info=True)\n \n", "is_truncated": false}
13edit_file
{
 "filepath": "src/httpx/_server.py",
 "new_string": "@contextlib.contextmanager\ndef serve_http(endpoint):\n    def handler(stream):\n        connection = HTTPConnection(stream, endpoint)\n        try:\n            connection.handle_requests()\n        finally:\n            connection.close()\n\n    logging.basicConfig(\n        format=\"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n        datefmt=\"%Y-%m-%d %H:%M:%S\",\n        level=logging.DEBUG\n    )\n\n    backend = NetworkBackend()\n    with backend.serve(\"127.0.0.1\", 8080, handler) as server:\n        server = HTTPServer(server.host, server.port)\n        logger.info(f\"Serving on {server.url} (Press CTRL+C to quit)\")\n        try:\n            yield server\n        except KeyboardInterrupt:\n            pass",
 "old_string": "@contextlib.contextmanager\ndef serve_http(endpoint):\n    def handler(stream):\n        connection = HTTPConnection(stream, endpoint)\n        connection.handle_requests()\n\n    logging.basicConfig(\n        format=\"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n        datefmt=\"%Y-%m-%d %H:%M:%S\",\n        level=logging.DEBUG\n    )\n\n    backend = NetworkBackend()\n    with backend.serve(\"127.0.0.1\", 8080, handler) as server:\n        server = HTTPServer(server.host, server.port)\n        logger.info(f\"Serving on {server.url} (Press CTRL+C to quit)\")\n        yield server"
}
{"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@@ -111,7 +111,10 @@\n def serve_http(endpoint):\n     def handler(stream):\n         connection = HTTPConnection(stream, endpoint)\n-        connection.handle_requests()\n+        try:\n+            connection.handle_requests()\n+        finally:\n+            connection.close()\n \n     logging.basicConfig(\n         format=\"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n@@ -123,7 +126,10 @@\n     with backend.serve(\"127.0.0.1\", 8080, handler) as server:\n         server = HTTPServer(server.host, server.port)\n         logger.info(f\"Serving on {server.url} (Press CTRL+C to quit)\")\n-        yield server\n+        try:\n+            yield server\n+        except KeyboardInterrupt:\n+            pass\n \n \n def run(app):\n", "is_truncated": false}
14submit_patch
[]
{"status": "ok", "patch_size": 2611, "files_changed": 2}
full
{"status": "ok", "patch_size": 2611, "files_changed": 2}

Patch

diff --git a/src/httpx/_parsers.py b/src/httpx/_parsers.py
index 830fccd..ae42b4e 100644
--- a/src/httpx/_parsers.py
+++ b/src/httpx/_parsers.py
@@ -109,6 +109,15 @@ class HTTPParser:
         # Special states...
         self.processing_1xx = False
 
+    @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 send_method_line(self, method: bytes, target: bytes, protocol: bytes) -> None:
         """
         Send the initial request line:
@@ -375,7 +384,7 @@ class HTTPParser:
             self.recv_state = State.DONE
         return body
 
-    def complete(self):
+    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..2da1326 100644
--- a/src/httpx/_server.py
+++ b/src/httpx/_server.py
@@ -49,6 +49,11 @@ class HTTPConnection:
                     else:
                         self._send_head(response)
                         self._send_body(response)
+                
+                if self._parser.keep_alive:
+                    # Read request to completion on keep alives
+                    while not self._parser.is_idle():
+                        self._parser.recv_body()
         except Exception:
             logger.error("Internal Server Error", exc_info=True)
 
@@ -89,7 +94,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
 
 
@@ -106,7 +111,10 @@ class HTTPServer:
 def serve_http(endpoint):
     def handler(stream):
         connection = HTTPConnection(stream, endpoint)
-        connection.handle_requests()
+        try:
+            connection.handle_requests()
+        finally:
+            connection.close()
 
     logging.basicConfig(
         format="%(levelname)s [%(asctime)s] %(name)s - %(message)s",
@@ -118,7 +126,10 @@ def serve_http(endpoint):
     with backend.serve("127.0.0.1", 8080, handler) as server:
         server = HTTPServer(server.host, server.port)
         logger.info(f"Serving on {server.url} (Press CTRL+C to quit)")
-        yield server
+        try:
+            yield server
+        except KeyboardInterrupt:
+            pass
 
 
 def run(app):

Test output

show
........................                                                 [100%]
24 passed in 0.08s