← pod_r3_v1g

httpx_3672

resolved RESOLVED PASS · 35 tool calls · 186 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 (36)

#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,150p' 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            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)\n\n        self.send_state = State.SEN
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,390p' 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        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", "stderr": "", "exit_code": 0}
5run_command
{
 "command": "sed -n '391,400p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " 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.rec
full
{"status": "ok", "stdout": "            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}
6run_command
{
 "command": "sed -n '27,100p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " self._parser = HTTPParser(stream, mode='SERVER')\n self._keepalive_duration = 5.0\n self._idle_expiry = time.monotonic() + self._ke
full
{"status": "ok", "stdout": "        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        ]\n        return m, t, h\n\n    def _recv_body(self):\n        return self._parser.recv_body()\n\n    # Return the response.
7run_command
{
 "command": "sed -n '101,130p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " while(True):\n sleep(1)\n\n\n@contextlib.contextmanager\ndef serve_http(endpoint):\n def handler(stream):\n connection = HTTPConnec
full
{"status": "ok", "stdout": "        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": "grep -n \"def __init__\" src/httpx/_parsers.py"
}
{"status": "ok", "stdout": "86: def __init__(self, stream: Stream, mode: str) -> None:\n433: def __init__(self, stream: Stream) -> None:\n", "stderr": "", "exit
full
{"status": "ok", "stdout": "86:    def __init__(self, stream: Stream, mode: str) -> None:\n433:    def __init__(self, stream: Stream) -> None:\n", "stderr": "", "exit_code": 0}
9run_command
{
 "command": "sed -n '86,100p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " def __init__(self, stream: Stream, mode: str) -> None:\n self.stream = stream\n self.parser = ReadAheadParser(stream)\n self.mode =
full
{"status": "ok", "stdout": "    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", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "sed -n '101,115p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " 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.
full
{"status": "ok", "stdout": "        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", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_parsers.py\")\ns = p.read_text()\n\n# Add keep_alive property\n# Find where to add it. Maybe after __init__ or before complete.\n# Let's add it before complete.\nold = \"\"\"    def complete(self):\"\"\"\nnew = \"\"\"    @property\n    def keep_alive(self) -> bool:\n        return self.send_keep_alive and self.recv_keep_alive\n\n    def complete(self):\"\"\"\n\n# Wait, the task says HTTPParser.complete -> .reset\n# Let's do that too.\n# But first, let's check if complete is used elsewhere.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "grep -rn \"complete(\" src/httpx/"
}
{"status": "ok", "stdout": "src/httpx/_streams.py:132: self._complete()\nsrc/httpx/_pool.py:177: # await self._cycle_complete()\nsrc/httpx/_pool.py:239: def _co
full
{"status": "ok", "stdout": "src/httpx/_streams.py:132:            self._complete()\nsrc/httpx/_pool.py:177:        #        await self._cycle_complete()\nsrc/httpx/_pool.py:239:    def _complete(self) -> None:\nsrc/httpx/_pool.py:240:        self._parser.complete()\nsrc/httpx/_parsers.py:83:    client.complete()\nsrc/httpx/_parsers.py:378:    def complete(self):\nsrc/httpx/_server.py:91:    def _complete(self):\n", "stderr": "", "exit_code": 0}
13run_command
{
 "command": "sed -n '91,95p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " def _complete(self):\n self._parser.complete\n self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n\n", "stderr": ""
full
{"status": "ok", "stdout": "    def _complete(self):\n        self._parser.complete\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n\n", "stderr": "", "exit_code": 0}
14run_command
{
 "command": "sed -n '239,241p' src/httpx/_pool.py"
}
{"status": "ok", "stdout": " def _complete(self) -> None:\n self._parser.complete()\n self._idle_expiry = time.monotonic() + self._keepalive_duration\n", "stder
full
{"status": "ok", "stdout": "    def _complete(self) -> None:\n        self._parser.complete()\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n", "stderr": "", "exit_code": 0}
15run_command
{
 "command": "grep -n \"def _complete\" src/httpx/_pool.py"
}
{"status": "ok", "stdout": "239: def _complete(self) -> None:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "239:    def _complete(self) -> None:\n", "stderr": "", "exit_code": 0}
16run_command
{
 "command": "command:<|\"|>sed -n '230"
}
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: -c: line 0: syntax error near unexpected token `|'\n/bin/bash: -c: line 0: `comma
full
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: -c: line 0: syntax error near unexpected token `|'\n/bin/bash: -c: line 0: `command:<|\"|>sed -n '230'\n", "details": {"stdout": "", "stderr": "/bin/bash: -c: line 0: syntax error near unexpected token `|'\n/bin/bash: -c: line 0: `command:<|\"|>sed -n '230'\n", "exit_code": 2}}
17run_command
{
 "command": "sed -n '230,250p' src/httpx/_pool.py"
}
{"status": "ok", "stdout": " (k.decode('ascii'), v.decode('ascii'))\n for k, v in h\n ])\n return code, headers\n\n def _recv_body(self) -> bytes:\n return self
full
{"status": "ok", "stdout": "            (k.decode('ascii'), v.decode('ascii'))\n            for k, v in h\n        ])\n        return code, headers\n\n    def _recv_body(self) -> bytes:\n        return self._parser.recv_body()\n\n    # Request/response cycle complete...\n    def _complete(self) -> None:\n        self._parser.complete()\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\n\n    def _close(self) -> None:\n        self._parser.close()\n\n    # Builtins...\n    def __repr__(self) -> str:\n        return f\"<Connection [{self._origin} {self.description()}]>\"\n\n    def __enter__(self) -> \"Connection\":\n", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_parsers.py\")\ns = p.read_text()\n\n# Add keep_alive property\n# Rename complete to reset\n# We need to replace 'def complete(self):' with 'def reset(self):'\n# and add the keep_alive property before it.\n\nold = \"\"\"    def complete(self):\"\"\"\nnew = \"\"\"    @property\n    def keep_alive(self) -> bool:\n        return self.send_keep_alive and self.recv_keep_alive\n\n    def reset(self):\"\"\"\n\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}
19run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\n# Fix _complete method: self._parser.complete -> self._parser.reset()\nold = \"\"\"    def _complete(self):\n        self._parser.complete\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\"\"\"\nnew = \"\"\"    def _complete(self):\n        self._parser.reset()\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\"\"\"\n\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}
20run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_pool.py\")\ns = p.read_text()\n\n# Fix _complete method: self._parser.complete() -> self._parser.reset()\nold = \"\"\"    def _complete(self) -> None:\n        self._parser.complete()\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\"\"\"\nnew = \"\"\"    def _complete(self) -> None:\n        self._parser.reset()\n        self._idle_expiry = time.monotonic() + self._keepalive_duration\"\"\"\n\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}
21run_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}
22run_command
{
 "command": "sed -n '110,130p' src/httpx/_server.py"
}
{"status": "ok", "stdout": "\n logging.basicConfig(\n format=\"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n datefmt=\"%Y-%m-%d %H:%M:%S\",\n level=lo
full
{"status": "ok", "stdout": "\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}
23run_command
{
 "command": "sed -n '101,110p' src/httpx/_server.py"
}
{"status": "ok", "stdout": " while(True):\n sleep(1)\n\n\n@contextlib.contextmanager\ndef serve_http(endpoint):\n def handler(stream):\n connection = HTTPConnec
full
{"status": "ok", "stdout": "        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", "stderr": "", "exit_code": 0}
24run_command
{
 "command": "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, we should ensure the request body is read if keep-alive is intended.\n# The 'with Request(...) as request:' block should handle reading the body.\n# But if an exception occurs, we might need to read the remaining body.\n\n# Let's look at handle_requests again.\n# If an exception occurs in the endpoint, we send a 500 response.\n# But we should still ensure the request body is fully consumed.\n\n# Also: \"Close streams on server exit.\"\n# In serve_http, the handler is called.\n# \"Don't raise KeyboardException on server exit.\"\n# This probably refers to how the server loop handles KeyboardInterrupt.\n\n# Let's modify handle_requests to ensure body is read.\n# And modify serve_http to handle KeyboardInterrupt.\n\n# For \"Close streams on server exit\", the backend.serve context manager should handle it,\n# but let's check if we need to do something in serve_http.\n\n# Let's fix handle_requests first.\n# If we are in a keep-alive connection, we must consume the request body.\n\n# In handle_requests:\n# with Request(method, url, headers=headers, content=stream) as request:\n#     ...\n# The Request.__exit__ should handle closing the stream.\n# But we need to make sure the body is read if we want to keep the connection alive.\n\n# Actually, the Request object in httpx usually reads the body.\n# Let's see how Request is implemented.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
25run_command
{
 "command": "grep -rn \"class Request\" src/httpx/"
}
{"status": "ok", "stdout": "src/httpx/_request.py:12:class Request:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "src/httpx/_request.py:12:class Request:\n", "stderr": "", "exit_code": 0}
26run_command
{
 "command": "sed -n '12,60p' 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 (\"
27run_command
{
 "command": "sed -n '61,100p' src/httpx/_request.py"
}
{"status": "ok", "stdout": " # Transfer-Encoding is sent and the request method defines a meaning for\n # an enclosed payload body. For example, a Content-Lengt
full
{"status": "ok", "stdout": "            # Transfer-Encoding is sent and the request method defines a meaning for\n            # an enclosed payload body. For example, a Content-Length header field is\n            # normally sent in a POST request even when the value is 0.\n            # (indicating an empty payload body).\n            self.headers = self.headers.copy_set(\"Content-Length\", \"0\")\n\n    @property\n    def body(self) -> bytes:\n        if not hasattr(self, '_body'):\n            raise RuntimeError(\"'.body' cannot be accessed without calling '.read()'\")\n        return self._body\n\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    def close(self) -> None:\n        self.stream.close()\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        traceback: types.TracebackType | None = None\n    ):\n        self.close()\n\n    def __repr__(self):\n        return f\"<Request [{self.method} {str(self.url)!r}]>\"\n", "stderr": "", "exit_code": 0}
28run_command
{
 "command": "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# We need to ensure that the request body is read before the next request.\n\n# In handle_requests:\n# with Request(method, url, headers=headers, content=stream) as request:\n#     ...\n# The Request.__exit__ calls self.close(), which calls self.stream.close().\n# But if the stream is the HTTPParser.recv_body, closing it might not consume it.\n\n# Let's check HTTPParser.recv_body.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
29run_command
{
 "command": "sed -n '300,350p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " value = value.strip(b\" \")\n headers.append((name, value))\n\n # Update header state\n seen_host = False\n for name, value in head
full
{"status": "ok", "stdout": "            value = value.strip(b\" \")\n            headers.append((name, value))\n\n        # Update header state\n        seen_host = False\n        for name, value in headers:\n            lname = name.lower()\n            if lname == b'host':\n                seen_host = True\n            elif lname == b'content-length':\n                self.recv_content_length = bounded_int(\n                    value,\n                    max_digits=20,\n                    exc_text=\"Received invalid Content-Length\"\n                )\n            elif lname == b'connection' and value == b'close':\n                self.recv_keep_alive = False\n            elif lname == b'transfer-encoding' and value == b'chunked':\n                self.recv_content_length = None\n\n        if self.mode == Mode.SERVER and not seen_host:\n            raise ProtocolError(\"Request missing 'Host' header\")\n\n        if self.processing_1xx:\n            # 1xx status codes preceed the final response status code\n            self.processing_1xx = False\n            self.recv_state = State.RECV_STATUS_LINE\n        else:\n            self.recv_state = State.RECV_BODY\n        return headers\n\n    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", "stderr": "", "exit_code": 0}
30run_command
{
 "command": "sed -n '351,377p' src/httpx/_parsers.py"
}
{"status": "ok", "stdout": " exc_text = \"Received invalid chunk size\"\n size = bounded_hex(sizestr, max_digits=8, exc_text=exc_text)\n if size > 0:\n body = s
full
{"status": "ok", "stdout": "            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", "stderr": "", "exit_code": 0}
31run_command
{
 "command": "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# We should read the request body if it hasn't been read yet.\n\n# In handle_requests:\n# with Request(method, url, headers=headers, content=stream) as request:\n#     ...\n# If the endpoint didn't read the body, we should read it now.\n\n# Let's modify handle_requests.\n\nold = \"\"\"                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# We need to add something to consume the body.\n# The Request object has a .read() method, but it's for reading the body into memory.\n# The stream is already passed to Request.\n# If the endpoint didn't read from the stream, we should read it.\n\n# Let's see if Request has a way to check if body was read.\n# It doesn't seem to.\n\n# But we can just call request.read() if it hasn't been read.\n# Wait, request.read() reads the stream.\n\nnew = \"\"\"                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                    # Always read request to completion on keep alives\n                    request.read()\"\"\"\n\n# Wait, if the endpoint already read the body, request.read() will just return the cached body.\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# Yes, it caches.\n\n# Now let's handle \"Close streams on server exit.\"\n# In serve_http:\n# with backend.serve(\"127.0.0.1\", 8080, handler) as server:\n#     ...\n#     yield server\n\n# And \"Don't raise KeyboardException on server exit.\"\n# This probably means catching KeyboardInterrupt in run().\n\n# Let's modify run().\n# def run(app):\n#     with serve_http(app) as server:\n#         server.wait()\n\n# Let's change it to:\n# def run(app):\n#     try:\n#         with serve_http(app) as server:\n#             server.wait()\n#     except KeyboardInterrupt:\n#         pass\n\n# Let's apply these changes.\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}
32run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\nold = \"\"\"def run(app):\n    with serve_http(app) as server:\n        server.wait()\"\"\"\nnew = \"\"\"def run(app):\n    try:\n        with serve_http(app) as server:\n            server.wait()\n    except KeyboardInterrupt:\n        pass\"\"\"\n\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}
33run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"src/httpx/_server.py\")\ns = p.read_text()\n\n# Close streams on server exit.\n# The serve_http function:\n# def serve_http(endpoint):\n#     def handler(stream):\n#         connection = HTTPConnection(stream, endpoint)\n#         connection.handle_requests()\n#\n#     ...\n#     with backend.serve(\"127.0.0.1\", 8080, handler) as server:\n#         ...\n#         yield server\n\n# The handler is called for each connection.\n# If the server exits, the backend.serve context manager should close the connections.\n# But maybe we should explicitly close the stream in the handler?\n\n# Let's see how handler is implemented.\n# def handler(stream):\n#     connection = HTTPConnection(stream, endpoint)\n#     connection.handle_requests()\n\n# If we wrap it in a try...finally:\n# def handler(stream):\n#     try:\n#         connection = HTTPConnection(stream, endpoint)\n#         connection.handle_requests()\n#     finally:\n#         stream.close()\n\nold = \"\"\"    def handler(stream):\n        connection = HTTPConnection(stream, endpoint)\n        connection.handle_requests()\"\"\"\nnew = \"\"\"    def handler(stream):\n        try:\n            connection = HTTPConnection(stream, endpoint)\n            connection.handle_requests()\n        finally:\n            stream.close()\"\"\"\n\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}
34run_command
{
 "command": "git diff"
}
{"status": "ok", "stdout": "diff --git a/src/httpx/_parsers.py b/src/httpx/_parsers.py\nindex 830fccd..61ac4cb 100644\n--- a/src/httpx/_parsers.py\n+++ b/src/ht
full
{"status": "ok", "stdout": "diff --git a/src/httpx/_parsers.py b/src/httpx/_parsers.py\nindex 830fccd..61ac4cb 100644\n--- a/src/httpx/_parsers.py\n+++ b/src/httpx/_parsers.py\n@@ -375,7 +375,11 @@ class HTTPParser:\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+    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 \ndiff --git a/src/httpx/_pool.py b/src/httpx/_pool.py\nindex 7193f8d..64f4fe0 100644\n--- a/src/httpx/_pool.py\n+++ b/src/httpx/_pool.py\n@@ -237,7 +237,7 @@ class Connection(Transport):\n \n     # Request/response cycle complete...\n     def _complete(self) -> None:\n-        self._parser.complete()\n+        self._parser.reset()\n         self._idle_expiry = time.monotonic() + self._keepalive_duration\n \n     def _close(self) -> None:\ndiff --git a/src/httpx/_server.py b/src/httpx/_server.py\nindex 95226d9..25181ab 100644\n--- a/src/httpx/_server.py\n+++ b/src/httpx/_server.py\n@@ -49,6 +49,8 @@ class HTTPConnection:\n                     else:\n                         self._send_head(response)\n                         self._send_body(response)\n+                    # Always read request to completion on keep alives\n+                    request.read()\n         except Exception:\n             logger.error(\"Internal Server Error\", exc_info=True)\n \n@@ -89,7 +91,7 @@ class HTTPConnection:\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@@ -105,8 +107,11 @@ class HTTPServer:\n @contextlib.contextmanager\n def serve_http(endpoint):\n     def handler(stream):\n-        connection = HTTPConnection(
35run_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}}
36submit_patch
{}
{"status": "ok", "patch_size": 2477, "files_changed": 3}
full
{"status": "ok", "patch_size": 2477, "files_changed": 3}

Patch

diff --git a/src/httpx/_parsers.py b/src/httpx/_parsers.py
index 830fccd..61ac4cb 100644
--- a/src/httpx/_parsers.py
+++ b/src/httpx/_parsers.py
@@ -375,7 +375,11 @@ class HTTPParser:
             self.recv_state = State.DONE
         return body
 
-    def complete(self):
+    @property
+    def keep_alive(self) -> bool:
+        return self.send_keep_alive and self.recv_keep_alive
+
+    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/_pool.py b/src/httpx/_pool.py
index 7193f8d..64f4fe0 100644
--- a/src/httpx/_pool.py
+++ b/src/httpx/_pool.py
@@ -237,7 +237,7 @@ class Connection(Transport):
 
     # Request/response cycle complete...
     def _complete(self) -> None:
-        self._parser.complete()
+        self._parser.reset()
         self._idle_expiry = time.monotonic() + self._keepalive_duration
 
     def _close(self) -> None:
diff --git a/src/httpx/_server.py b/src/httpx/_server.py
index 95226d9..25181ab 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)
+                    # Always read request to completion on keep alives
+                    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
 
 
@@ -105,8 +107,11 @@ class HTTPServer:
 @contextlib.contextmanager
 def serve_http(endpoint):
     def handler(stream):
-        connection = HTTPConnection(stream, endpoint)
-        connection.handle_requests()
+        try:
+            connection = HTTPConnection(stream, endpoint)
+            connection.handle_requests()
+        finally:
+            stream.close()
 
     logging.basicConfig(
         format="%(levelname)s [%(asctime)s] %(name)s - %(message)s",
@@ -122,5 +127,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