1
0
mirror of https://github.com/sredevopsorg/deepseek-kubernetes-sandbox.git synced 2026-11-03 01:13:24 +00:00

Do not read-time-out proxied upstream streams (#9)

Every proxied response is streamed through _relay using one shared
httpx.AsyncClient configured with timeout=HTTP_TIMEOUT (300s). An idle SSE
or long-poll stream therefore raised httpx.ReadTimeout every five minutes,
which escaped the StreamingResponse as a full traceback and truncated the
browser's stream.

Build proxied requests with a timeout that leaves read unbounded (connect,
write and pool stay bounded), and end the relay quietly on httpx.HTTPError
instead of propagating it. The relay is cancelled when the browser
disconnects and _relay closes the upstream response, so an unbounded read
cannot outlive the client.

Docs: note the split in the HTTP_TIMEOUT row.

Co-authored-by: SREDevOpsAgents <agents@sredevops.org>
This commit is contained in:
SREDevOps.org Agent
2026-10-21 14:49:55 -03:00
committed by GitHub
parent 687640621e
commit 163abaeac0
3 changed files with 89 additions and 4 deletions

View File

@@ -1105,6 +1105,65 @@ async def test_proxy_cookie_mutation_requires_same_origin(env):
assert good.status_code == 200
def test_stream_timeout_leaves_read_unbounded(env):
timeout = env.gateway._stream_timeout()
assert timeout.read is None
assert timeout.connect == env.settings.http_timeout
@pytest.mark.asyncio
async def test_proxied_requests_use_the_stream_timeout(env, monkeypatch):
from starlette.requests import Request
captured = {}
real_build = env.gateway.http.build_request
def spy_build(method, url, **kwargs):
captured.update(kwargs)
return real_build(method, url, **kwargs)
monkeypatch.setattr(env.gateway.http, "build_request", spy_build)
scope = {
"type": "http",
"method": "GET",
"scheme": "http",
"path": "/u/alice/events",
"raw_path": b"/u/alice/events",
"query_string": b"",
"root_path": "",
"headers": [(b"host", b"gateway.test")],
"client": ("127.0.0.1", 4321),
"server": ("gateway.test", 80),
"path_params": {"user": "alice", "path": "events"},
}
response = await env.gateway._send_upstream(
Request(scope), "alice", FQDN, "dsh-auth-a=1", b""
)
await response.aclose()
assert captured["timeout"].read is None
@pytest.mark.asyncio
async def test_relay_ends_cleanly_when_the_upstream_stream_breaks(env):
class Broken:
closed = False
async def aiter_raw(self):
yield b"chunk"
raise httpx.ReadTimeout("upstream went quiet")
async def aclose(self):
self.closed = True
upstream = Broken()
chunks = [chunk async for chunk in env.gateway._relay("alice", upstream)]
assert chunks == [b"chunk"]
assert upstream.closed is True
assert env.gateway.state.in_flight["alice"] == 0
# --- WebSocket proxy -------------------------------------------------------