resolved RESOLVED PASS · 13 tool calls · 136 s · encode/httpx
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 | Arguments | Result |
|---|---|---|---|
| 1 | run_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/_pafull{"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} |
| 2 | read_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_mefull{"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)\ |
| 3 | read_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 sfull{"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_ |
| 4 | read_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 statfull{"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_ |
| 5 | read_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 |
| 6 | read_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 imfull{"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)\ |
| 7 | read_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 handlfull{"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} |
| 8 | edit_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\nfull{"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} |
| 9 | edit_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\nfull{"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} |
| 10 | read_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_closedfull{"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 |
| 11 | edit_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} |
| 12 | edit_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} |
| 13 | edit_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} |
| 14 | submit_patch | [] | {"status": "ok", "patch_size": 2611, "files_changed": 2}full{"status": "ok", "patch_size": 2611, "files_changed": 2} |
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):
........................ [100%] 24 passed in 0.08s