← pod_r5_v1h

httpx_3672

resolved RESOLVED PASS · 39 tool calls · 258 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 (40)

#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}
2run_command
{
 "command": "sed -n '72,120p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": "class HTTPParser:\n \"\"\"\n Usage...\n\n client = HTTPParser(writer, reader)\n client.send_method_line()\n client.send_headers()\n
full
{"status": "ok", "stdout": "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", "stderr": "", "exit_code": 0}
3run_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}
4run_command
{
 "command": "sed -n '378,385p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " def complete(self):\n is_fully_complete = self.send_state == State.DONE and self.recv_state == State.DONE\n is_keepalive = self.sen
full
{"status": "ok", "stdout": "    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", "stderr": "", "exit_code": 0}
5run_command
{
 "command": "grep -n \"class Server\" src/httpx/_server.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}}
6run_command
{
 "command": "grep -n \"class \" src/httpx/_server.py"
}
{"status": "ok", "stdout": "19:class ConnectionClosed(Exception):\n23:class HTTPConnection:\n96:class HTTPServer:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "19:class ConnectionClosed(Exception):\n23:class HTTPConnection:\n96:class HTTPServer:\n", "stderr": "", "exit_code": 0}
7run_command
{
 "command": "sed -n '96,160p' src/httpx/_server.py"
}
{"status": "ok", "stdout": "class HTTPServer:\n def __init__(self, host, port):\n self.url = f\"http://{host}:{port}/\"\n\n def wait(self):\n while(True):\n sle
full
{"status": "ok", "stdout": "class HTTPServer:\n    def __init__(self, host, port):\n        self.url = f\"http://{host}:{port}/\"\n\n    def wait(self):\n        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", "stderr": "", "exit_code": 0}
8run_command
{
 "command": "sed -n '23,95p' src/httpx/_server.py"
}
{"status": "ok", "stdout": "class HTTPConnection:\n def __init__(self, stream, endpoint):\n self._stream = stream\n self._endpoint = endpoint\n self._parser = H
full
{"status": "ok", "stdout": "class 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)\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 
9run_command
{
 "command": "sed -n '380,395p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " 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 retu
full
{"status": "ok", "stdout": "        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", "stderr": "", "exit_code": 0}
10run_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}}
11run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_parsers.py\")\ns = p.read_text()\nold = \"\"\"    def complete(self):\"\"\"\nnew = \"\"\"    def reset(self):\"\"\"\ns = s.replace(old, new)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\nold = \"\"\"        self._parser.complete\"\"\"\nnew = \"\"\"        self._parser.reset()\"\"\"\ns = s.replace(old, new)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
13run_command
{
 "command": "sed -n '72,110p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": "class HTTPParser:\n \"\"\"\n Usage...\n\n client = HTTPParser(writer, reader)\n client.send_method_line()\n client.send_headers()\n
full
{"status": "ok", "stdout": "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", "stderr": "", "exit_code": 0}
14run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_parsers.py\")\ns = p.read_text()\nold = \"\"\"    client.complete()\"\"\"\nnew = \"\"\"    client.reset()\"\"\"\ns = s.replace(old, new)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
15run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_parsers.py\")\ns = p.read_text()\n\n# Add keep_alive property\n# Find the end of __init__ and insert property\n# __init__ ends around line 108\n# Let's find a good place to insert.\n\n# The current __init__ ends with:\n#         self.processing_1xx = False\n\n# Let's insert after __init__\n# Looking at the file, send_method_line comes after __init__\n\n# I'll use a search and replace to insert the property\n# We can put it before send_method_line\n\n# Let's search for 'def send_method_line'\nimport re\npattern = r'(def send_method_line\\(self, method: bytes, target: bytes, protocol: bytes\\) -> None:)'\nreplacement = r'\\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\\1'\n\n# Since I don't have a safe way to insert with re.sub and keep line numbers exactly,\n# let's just find the line and use a replacement.\n\n# Actually, let's just add it as a method/property.\n# Let's check where send_method_line is.\n# grep -n \"def send_method_line\" src/httpx/_parsers.py\n# it's around 111\n\n# Let's just replace:\n#    def send_method_line(self, method: bytes, target: bytes, protocol: bytes) -> None:\n# with\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\nold_text = \"    def send_method_line(self, method: bytes, target: bytes, protocol: bytes) -> None:\"\nnew_text = \"\"\"    @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\nif old_text in s:\n    s = s.replace(old_text, new_text)\n    p.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
16run_command
{
 "command": "sed -n '110,125p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " 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\
full
{"status": "ok", "stdout": "        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        Send the initial request line:\n\n        >>> p.send_method_line(b'GET', b'/', b'HTTP/1.1')\n", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "sed -n '30,60p' src/httpx/_server.py"
}
{"status": "ok", "stdout": "\n # API entry points...\n def handle_requests(self):\n try:\n while not self._parser.is_closed():\n method, url, headers = self._re
full
{"status": "ok", "stdout": "\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}
18run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\n# Server... always read request to completion on keep alives.\n# In handle_requests, if keepalive, we should ensure the body is read.\n\n# Current handle_requests:\n#                 with Request(method, url, headers=headers, content=stream) as request:\n#                     try:\n#                         response = self._endpoint(request)\n#                         ...\n#                     except Exception:\n#                         ...\n#                     else:\n#                         ...\n\n# If Request is a context manager, it might handle reading the body? \n# Let's check src/httpx/_request.py or wherever Request is defined.\n\n# Wait, the task says \"Server... always read request to completion on keep alives.\"\n# This usually means that even if the endpoint doesn't read the request body, \n# we must consume it before the next request on the same connection.\n\n# Let's see how Request context manager is implemented.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
19run_command
{
 "command": "grep -n \"class Request\" src/httpx/_request.py"
}
{"status": "ok", "stdout": "12:class Request:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "12:class Request:\n", "stderr": "", "exit_code": 0}
20run_command
{
 "command": "sed -n '12,150p' src/httpx/_request.py"
}
{"status": "ok", "stdout": "class Request:\n def __init__(\n self,\n method: str,\n url: URL | str,\n headers: Headers | typing.Mapping[str, str] | None = None,
full
{"status": "ok", "stdout": "class Request:\n    def __init__(\n        self,\n        method: str,\n        url: URL | str,\n        headers: Headers | typing.Mapping[str, str] | None = None,\n        content: Content | Stream | bytes | None = None,\n    ):\n        self.method = method\n        self.url = URL(url)\n        self.headers = Headers(headers)\n        self.stream: Stream = ByteStream(b\"\")\n\n        # https://datatracker.ietf.org/doc/html/rfc2616#section-14.23\n        #\u00a0RFC 2616, Section 14.23, Host.\n        #\n        # A client MUST include a Host header field in all HTTP/1.1 request messages.\n        if \"Host\" not in self.headers:\n            self.headers = self.headers.copy_set(\"Host\", self.url.netloc)\n\n        if content is not None:\n            if isinstance(content, bytes):\n                self.stream = ByteStream(content)\n            elif isinstance(content, Stream):\n                self.stream = content\n            elif isinstance(content, Content):\n                ct = content.content_type()\n                self.stream = content.encode()\n                self.headers = self.headers.copy_set(\"Content-Type\", ct)\n            else:\n                raise TypeError(f'Expected `Content | Stream | bytes | None` got {type(content)}')\n\n            # https://datatracker.ietf.org/doc/html/rfc2616#section-4.3\n            #\u00a0RFC 2616, Section 4.3, Message Body.\n            #\n            # The presence of a message-body in a request is signaled by the\n            # inclusion of a Content-Length or Transfer-Encoding header field in\n            # the request's message-headers.\n            content_length: int | None = self.stream.size\n            if content_length is None:\n                self.headers = self.headers.copy_set(\"Transfer-Encoding\", \"chunked\")\n            elif content_length > 0:\n                self.headers = self.headers.copy_set(\"Content-Length\", str(content_length))\n\n        elif method in (\"
21run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\n# We need to ensure the request body is read to completion on keepalives.\n# In handle_requests, we can call request.stream.read() or similar in the __exit__ of Request?\n# But Request is a simple wrapper.\n\n# Let's modify handle_requests in src/httpx/_server.py to consume the body.\n\n# Current:\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 the parser is keepalive, we should consume any remaining body.\n# Wait, the Request.__exit__ calls self.close(), which calls self.stream.close().\n# If the stream is the one from HTTPParser.recv_body, closing it might not be enough.\n# We need to actually read the bytes.\n\n# Let's check HTTPParser.recv_body.\n\n# Let's look at src/httpx/_parsers.py to see how recv_body is implemented.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
22run_command
{
 "command": "grep -n \"def recv_body\" src/httpx/_parsers.py"
}
{"status": "ok", "stdout": "340: def recv_body(self) -> bytes:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "340:    def recv_body(self) -> bytes:\n", "stderr": "", "exit_code": 0}
23run_command
{
 "command": "sed -n '340,377p' src/httpx/_parsers.py"
}
{"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", "stderr": "", "exit_code": 0}
24run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\n# Let's implement \"Server... always read request to completion on keep alives.\"\n# We can do this by calling request.read() or consuming the stream in handle_requests.\n\n# I'll modify handle_requests to consume the body if it's keepalive.\n\n# The loop is:\n# while not self._parser.is_closed():\n#     ...\n#     with Request(...) as request:\n#         ...\n\n# We can add:\n# if self._parser.keep_alive:\n#     request.read() # This reads all remaining bytes\n\n# Let's find where to insert it. After the try-except-else block.\n\n# Current structure:\n#                 with Request(method, url, headers=headers, content=stream) as request:\n#                     try:\n#                         ...\n#                     except Exception:\n#                         ...\n#                     else:\n#                         ...\n#                 # <--- INSERT HERE\n\n# Wait, Request is a context manager. When it exits, it calls close().\n# If we want to read the remaining body, we should do it before it closes?\n# Actually, if we call request.read(), it reads everything and stores it in self._body.\n\n# Let's check Request.read() again.\n# def read(self) -> bytes:\n#     if not hasattr(self, '_body'):\n#         self._body = self.stream.read()\n#         self.stream = ByteStream(self._body)\n#     return self._body\n\n# And stream is what we passed in.\n\n# Let's modify src/httpx/_server.py.\n\nold_block = \"\"\"                        self._send_body(response)\"\"\"\nnew_block = \"\"\"                        self._send_body(response)\\n                    if self._parser.keep_alive:\\n                        request.read()\"\"\"\n# This is inside the 'else' block. But we should probably do it even if an exception occurred.\n\n# Better place: outside the try-except-else but inside the 'with' block?\n# Or after the 'with' block? But then the stream might be closed.\n\n# Let's do it inside the 'with' block, after the try-except-else.\n\n# Wait, if we are in 'else', we already sent the response.\n# If we were in 'except', we also sent the response.\n\n# So:\n#                 with Request(method, url, headers=headers, content=stream) as request:\n#                     try:\n#                         ...\n#                     except Exception:\n#                         ...\n#                     else:\n#                         ...\n#                     if self._parser.keep_alive:\\n                        request.read()\n\n# Let's refine the replacement.\n\nimport re\n# We want to replace:\n#                     else:\n#                         self._send_head(response)\n#                         self._send_body(response)\n# with:\n#                     else:\n#                         self._send_head(response)\n#                         self._send_body(response)\n#                     if self._parser.keep_alive:\n#                         request.read()\n\n# Let's find the exact lines.\n\n# Lines 46-47 in the previous sed output:\n#                     else:\n#                         self._send_head(response)\n#                         self._send_body(response)\n\n# Let's use a more robust replacement.\n\n# The line before 'else:' is:\n#                         self._send_body(err)\n\n# Let's try this:\nold_text = \"\"\"                    else:\n                        self._send_head(response)\n                        self._send_body(response)\"\"\"\nnew_text = \"\"\"                    else:\n                        self._send_head(response)\n                        self._send_body(response)\n                    if self._parser.keep_alive:\n                        request.read()\"\"\"\n\nif old_text in s:\n    s = s.replace(old_text, new_text)\n    p.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
25run_command
{
 "command": "sed -n '40,60p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " response = self._endpoint(request)\n status_line = f\"{request.method} {request.url.target} [{response.status_code} {response.reaso
full
{"status": "ok", "stdout": "                        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                    if self._parser.keep_alive:\n                        request.read()\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", "stderr": "", "exit_code": 0}
26run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\n# Close streams on server exit.\n# Don't raise KeyboardException on server exit.\n\n# Let's look at serve_http and run.\n\n# Current serve_http:\n# @contextlib.contextmanager\n# def serve_http(endpoint):\n#     def handler(stream):\n#         connection = HTTPConnection(stream, endpoint)\n#         connection.handle_requests()\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# Current run:\n# def run(app):\n#     with serve_http(app) as server:\n#         server.wait()\n\n# The \"Close streams on server exit\" and \"Don't raise KeyboardException on server exit\" \n# probably refers to how the server handles shutdown.\n\n# If we press CTRL+C, KeyboardInterrupt is raised. \n# If we catch it in run(), we can avoid it propagating.\n\n# Let's modify run() in src/httpx/_server.py.\n\n# Current run:\n# def run(app):\n#     with serve_http(app) as server:\n#         server.wait()\n\n# Modified run:\n# def run(app):\n#     try:\n#         with serve_http(app) as server:\n#             server.wait()\n#     except KeyboardInterrupt:\n#         pass\n\n# Also, \"Close streams on server exit\". \n# The `backend.serve` context manager probably handles closing the listening socket.\n# But what about the active connections?\n\n# The `handler` function is:\n# def handler(stream):\n#     connection = HTTPConnection(stream, endpoint)\n#     connection.handle_requests()\n\n# If the server exits, we want to make sure all streams are closed.\n# Depending on how NetworkBackend.serve is implemented, it might already do this.\n# But the task specifically asks for it.\n\n# Let's check src/httpx/_network.py to see how NetworkBackend.serve works.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
27run_command
{
 "command": "grep -n \"class NetworkBackend\" src/httpx/_network.py"
}
{"status": "ok", "stdout": "193:class NetworkBackend:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "193:class NetworkBackend:\n", "stderr": "", "exit_code": 0}
28run_command
{
 "command": "sed -n '193,250p' src/httpx/_network.py"
}
{"status": "ok", "stdout": "class NetworkBackend:\n def __init__(self, ssl_ctx: ssl.SSLContext | None = None):\n self._ssl_ctx = self.create_default_context() i
full
{"status": "ok", "stdout": "class NetworkBackend:\n    def __init__(self, ssl_ctx: ssl.SSLContext | None = None):\n        self._ssl_ctx = self.create_default_context() if ssl_ctx is None else ssl_ctx\n\n    def create_default_context(self) -> ssl.SSLContext:\n        import certifi\n        return ssl.create_default_context(cafile=certifi.where())\n\n    def connect(self, host: str, port: int) -> NetworkStream:\n        \"\"\"\n        Connect to the given address, returning a NetworkStream instance.\n        \"\"\"\n        address = (host, port)\n        timeout = get_current_timeout()\n        sock = socket.create_connection(address, timeout=timeout)\n        return NetworkStream(sock, address)\n\n    def connect_tls(self, host: str, port: int, hostname: str = '') -> NetworkStream:\n        \"\"\"\n        Connect to the given address, returning a NetworkStream instance.\n        \"\"\"\n        address = (host, port)\n        hostname = hostname or host\n        timeout = get_current_timeout()\n        sock = socket.create_connection(address, timeout=timeout)\n        sock = self._ssl_ctx.wrap_socket(sock, server_hostname=hostname)\n        return NetworkStream(sock, address)\n\n    def listen(self, host: str, port: int) -> NetworkListener:\n        \"\"\"\n        List on the given address, returning a NetworkListener instance.\n        \"\"\"\n        address = (host, port)\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        sock.bind(address)\n        sock.listen(5)\n        sock.setblocking(False)\n        return NetworkListener(sock, address)\n\n    def serve(self, host: str, port: int, handler: typing.Callable[[NetworkStream], None]) -> NetworkServer:\n        listener = self.listen(host, port)\n        return NetworkServer(listener, handler)\n\n    def __repr__(self):\n        return \"<NetworkBackend [threaded]>\"\n\n\nSemaphore = threading.Semaphore\nLock = threa
29run_command
{
 "command": "grep -n \"class NetworkServer\" src/httpx/_network.py"
}
{"status": "ok", "stdout": "156:class NetworkServer:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "156:class NetworkServer:\n", "stderr": "", "exit_code": 0}
30run_command
{
 "command": "sed -n '156,200p' src/httpx/_network.py"
}
{"status": "ok", "stdout": "class NetworkServer:\n def __init__(self, listener: NetworkListener, handler: typing.Callable[[NetworkStream], None]) -> None:\n sel
full
{"status": "ok", "stdout": "class NetworkServer:\n    def __init__(self, listener: NetworkListener, handler: typing.Callable[[NetworkStream], None]) -> None:\n        self.listener = listener\n        self.handler = handler\n        self._max_workers = 5\n        self._executor = None\n        self._thread = None\n        self._streams = list[NetworkStream]\n\n    @property\n    def host(self):\n        return self.listener.host\n\n    @property\n    def port(self):\n        return self.listener.port\n\n    def __enter__(self):\n        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=self._max_workers)\n        self._executor.submit(self._serve)\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.listener.close()\n        self._executor.shutdown(wait=True)\n\n    def _serve(self):\n        while stream := self.listener.accept():\n            self._executor.submit(self._handler, stream)\n\n    def _handler(self, stream):\n        try:\n            self.handler(stream)\n        finally:\n            stream.close()\n\n\nclass NetworkBackend:\n    def __init__(self, ssl_ctx: ssl.SSLContext | None = None):\n        self._ssl_ctx = self.create_default_context() if ssl_ctx is None else ssl_ctx\n\n    def create_default_context(self) -> ssl.SSLContext:\n        import certifi\n        return ssl.create_default_context(cafile=certifi.where())\n\n", "stderr": "", "exit_code": 0}
31run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\n# Let's implement \"Don't raise KeyboardException on server exit.\"\n# In run(), catch KeyboardInterrupt.\n\n# Current run:\n# def run(app):\n#     with serve_http(app) as server:\n#         server.wait()\n\n# Modified run:\n# def run(app):\n#     try:\n#         with serve_http(app) as server:\n#             server.wait()\n#     except KeyboardInterrupt:\n#         pass\n\nold_run = \"\"\"def run(app):\n    with serve_http(app) as server:\n        server.wait()\"\"\"\nnew_run = \"\"\"def run(app):\n    try:\n        with serve_http(app) as server:\n            server.wait()\n    except KeyboardInterrupt:\n        pass\"\"\"\n\nif old_run in s:\n    s = s.replace(old_run, new_run)\n    p.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
32run_command
{
 "command": "sed -n '100,110p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " self.url = f\"http://{host}:{port}/\"\n\n def wait(self):\n while(True):\n sleep(1)\n\n\n@contextlib.contextmanager\ndef serve_http
full
{"status": "ok", "stdout": "        self.url = f\"http://{host}:{port}/\"\n\n    def wait(self):\n        while(True):\n            sleep(1)\n\n\n@contextlib.contextmanager\ndef serve_http(endpoint):\n    def handler(stream):\n        connection = HTTPConnection(stream, endpoint)\n", "stderr": "", "exit_code": 0}
33run_command
{
 "command": "sed -n '120,130p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " with backend.serve(\"127.0.0.1\", 8080, handler) as server:\n server = HTTPServer(server.host, server.port)\n logger.info(f\"Servin
full
{"status": "ok", "stdout": "    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    try:\n        with serve_http(app) as server:\n            server.wait()\n    except KeyboardInterrupt:\n", "stderr": "", "exit_code": 0}
34run_command
{
 "command": "sed -n '130,135p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " except KeyboardInterrupt:\n pass\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "    except KeyboardInterrupt:\n        pass\n", "stderr": "", "exit_code": 0}
35run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_network.py\")\ns = p.read_text()\n\n# \"Close streams on server exit.\"\n# In NetworkServer.__exit__, we close the listener and shutdown the executor.\n# But we should also close all active streams.\n\n# Let's add a way to track active streams in NetworkServer.\n\n# Current NetworkServer.__init__:\n#     def __init__(self, listener: NetworkListener, handler: typing.Callable[[NetworkStream], None]) -> None:\n#         self.listener = listener\n#         self.handler = handler\n#         self._max_workers = 5\n#         self._executor = None\n#         self._thread = None\n#         self._streams = list[NetworkStream] # This is a type hint for a list, not an actual list.\n\n# Wait, look at:\n#         self._streams = list[NetworkStream]\n# This is actually assigning the type 'list[NetworkStream]' to the attribute '_streams', not an empty list. \n# That's a bug! It should be `self._streams = []`.\n\n# And we need to add streams to this list in _handler and remove them when done.\n\n# Let's fix the init and modify _handler.\n\n# Current _handler:\n#     def _handler(self, stream):\n#         try:\n#             self.handler(stream)\n#         finally:\n#             stream.close()\n\n# Modified _handler:\n#     def _handler(self, stream):\n#         self._streams.append(stream)\n#         try:\n#             self.handler(stream)\n#         finally:\n#             self._streams.remove(stream)\n#             stream.close()\n\n# And modify __exit__:\n#     def __exit__(self, exc_type, exc_val, exc_tb):\n#         self.listener.close()\n#         for stream in self._streams:\n#             stream.close()\n#         self._executor.shutdown(wait=True)\n\n# Let's apply these changes.\n\nimport re\n\n# 1. Fix __init__\n# old: self._streams = list[NetworkStream]\n# new: self._streams = []\n\n# 2. Fix _handler\n# replace the whole _handler method.\n\n# 3. Fix __exit__\n# replace the whole __exit__ method.\n\n# Let's do them one by one.\n\n# Fix __init__\nold_init = \"        self._streams = list[NetworkStream]\"\nnew_init = \"        self._streams = []\"\ns = s.replace(old_init, new_init)\n\n# Fix _handler\nold_handler = \"\"\"    def _handler(self, stream):\n        try:\n            self.handler(stream)\n        finally:\n            stream.close()\"\"\"\nnew_handler = \"\"\"    def _handler(self, stream):\n        self._streams.append(stream)\n        try:\n            self.handler(stream)\n        finally:\n            self._streams.remove(stream)\n            stream.close()\"\"\"\ns = s.replace(old_handler, new_handler)\n\n# Fix __exit__\nold_exit = \"\"\"    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.listener.close()\n        self._executor.shutdown(wait=True)\"\"\"\nnew_exit = \"\"\"    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.listener.close()\n        for stream in self._streams:\n            stream.close()\n        self._executor.shutdown(wait=True)\"\"\"\ns = s.replace(old_exit, new_exit)\n\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
36run_command
{
 "command": "sed -n '160,185p' src/httpx/_network.py"
}
{"status": "ok", "stdout": " self._max_workers = 5\n self._executor = None\n self._thread = None\n self._streams = []\n\n @property\n def host(self):\n return s
full
{"status": "ok", "stdout": "        self._max_workers = 5\n        self._executor = None\n        self._thread = None\n        self._streams = []\n\n    @property\n    def host(self):\n        return self.listener.host\n\n    @property\n    def port(self):\n        return self.listener.port\n\n    def __enter__(self):\n        self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=self._max_workers)\n        self._executor.submit(self._serve)\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.listener.close()\n        for stream in self._streams:\n            stream.close()\n        self._executor.shutdown(wait=True)\n\n    def _serve(self):\n        while stream := self.listener.accept():\n", "stderr": "", "exit_code": 0}
37run_command
{
 "command": "sed -n '185,200p' src/httpx/_network.py"
}
{"status": "ok", "stdout": " while stream := self.listener.accept():\n self._executor.submit(self._handler, stream)\n\n def _handler(self, stream):\n self._stre
full
{"status": "ok", "stdout": "        while stream := self.listener.accept():\n            self._executor.submit(self._handler, stream)\n\n    def _handler(self, stream):\n        self._streams.append(stream)\n        try:\n            self.handler(stream)\n        finally:\n            self._streams.remove(stream)\n            stream.close()\n\n\nclass NetworkBackend:\n    def __init__(self, ssl_ctx: ssl.SSLContext | None = None):\n        self._ssl_ctx = self.create_default_context() if ssl_ctx is None else ssl_ctx\n\n", "stderr": "", "exit_code": 0}
38run_command
{
 "command": "git diff"
}
{"status": "ok", "stdout": "diff --git a/src/httpx/_network.py b/src/httpx/_network.py\nindex 5ea9bb5..e424b7a 100644\n--- a/src/httpx/_network.py\n+++ b/src/ht
full
{"status": "ok", "stdout": "diff --git a/src/httpx/_network.py b/src/httpx/_network.py\nindex 5ea9bb5..e424b7a 100644\n--- a/src/httpx/_network.py\n+++ b/src/httpx/_network.py\n@@ -160,7 +160,7 @@ class NetworkServer:\n         self._max_workers = 5\n         self._executor = None\n         self._thread = None\n-        self._streams = list[NetworkStream]\n+        self._streams = []\n \n     @property\n     def host(self):\n@@ -177,6 +177,8 @@ class NetworkServer:\n \n     def __exit__(self, exc_type, exc_val, exc_tb):\n         self.listener.close()\n+        for stream in self._streams:\n+            stream.close()\n         self._executor.shutdown(wait=True)\n \n     def _serve(self):\n@@ -184,9 +186,11 @@ class NetworkServer:\n             self._executor.submit(self._handler, stream)\n \n     def _handler(self, stream):\n+        self._streams.append(stream)\n         try:\n             self.handler(stream)\n         finally:\n+            self._streams.remove(stream)\n             stream.close()\n \n \ndiff --git a/src/httpx/_parsers.py b/src/httpx/_parsers.py\nindex 830fccd..953348a 100644\n--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n@@ -80,7 +80,7 @@ class HTTPParser:\n     client.recv_status_line()\n     client.recv_headers()\n     client.recv_body()\n-    client.complete()\n+    client.reset()\n     client.close()\n     \"\"\"\n     def __init__(self, stream: Stream, mode: str) -> None:\n@@ -109,6 +109,15 @@ class HTTPParser:\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         Send the initial request line:\n@@ -375,7 +384,7 @@ class HTTPPars
39run_command
{
 "command": "submit_patch"
}
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bas
full
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}}
40submit_patch
{}
{"status": "ok", "patch_size": 3166, "files_changed": 3}
full
{"status": "ok", "patch_size": 3166, "files_changed": 3}

Patch

diff --git a/src/httpx/_network.py b/src/httpx/_network.py
index 5ea9bb5..e424b7a 100644
--- a/src/httpx/_network.py
+++ b/src/httpx/_network.py
@@ -160,7 +160,7 @@ class NetworkServer:
         self._max_workers = 5
         self._executor = None
         self._thread = None
-        self._streams = list[NetworkStream]
+        self._streams = []
 
     @property
     def host(self):
@@ -177,6 +177,8 @@ class NetworkServer:
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         self.listener.close()
+        for stream in self._streams:
+            stream.close()
         self._executor.shutdown(wait=True)
 
     def _serve(self):
@@ -184,9 +186,11 @@ class NetworkServer:
             self._executor.submit(self._handler, stream)
 
     def _handler(self, stream):
+        self._streams.append(stream)
         try:
             self.handler(stream)
         finally:
+            self._streams.remove(stream)
             stream.close()
 
 
diff --git a/src/httpx/_parsers.py b/src/httpx/_parsers.py
index 830fccd..953348a 100644
--- a/src/httpx/_parsers.py
+++ b/src/httpx/_parsers.py
@@ -80,7 +80,7 @@ class HTTPParser:
     client.recv_status_line()
     client.recv_headers()
     client.recv_body()
-    client.complete()
+    client.reset()
     client.close()
     """
     def __init__(self, stream: Stream, mode: str) -> None:
@@ -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..eae3473 100644
--- a/src/httpx/_server.py
+++ b/src/httpx/_server.py
@@ -49,6 +49,8 @@ class HTTPConnection:
                     else:
                         self._send_head(response)
                         self._send_body(response)
+                    if self._parser.keep_alive:
+                        request.read()
         except Exception:
             logger.error("Internal Server Error", exc_info=True)
 
@@ -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
 
 
@@ -122,5 +124,8 @@ def serve_http(endpoint):
 
 
 def run(app):
-    with serve_http(app) as server:
-        server.wait()
+    try:
+        with serve_http(app) as server:
+            server.wait()
+    except KeyboardInterrupt:
+        pass

Test output

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