Skip to content

Commit be6e29c

Browse files
feat(scripts): local frontend dev server with prod backend proxy
Serves web/ from the working tree and forwards the data and chat routes to the live deployment, so uncommitted frontend and prompt changes can be exercised against the real model and corpus before any image bake. Prompt-behavior changes were previously only testable post-deploy (~15 min per iteration). python scripts/web_dev_proxy.py # http://127.0.0.1:8765
1 parent 192d9e9 commit be6e29c

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

scripts/web_dev_proxy.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Local web/ with the PROD backend proxied behind it.
2+
3+
Serves the working-tree frontend while forwarding /health /papers /figures
4+
/pages/* /query /demo/chat to the live deployment — full end-to-end testing
5+
of uncommitted client code against the real model and corpus, no deploy.
6+
"""
7+
8+
import functools
9+
import http.server
10+
import urllib.error
11+
import urllib.request
12+
from pathlib import Path
13+
14+
PROD = "https://spectrarag-ar6wxit42a-ew.a.run.app"
15+
ROOT = str(Path(__file__).resolve().parents[1] / "web")
16+
17+
18+
class H(http.server.SimpleHTTPRequestHandler):
19+
protocol_version = "HTTP/1.0" # close-delimited responses, safe for SSE relay
20+
21+
def _proxy(self) -> None:
22+
data = None
23+
if self.command == "POST":
24+
data = self.rfile.read(int(self.headers.get("Content-Length", 0)))
25+
req = urllib.request.Request(PROD + self.path, data=data, method=self.command)
26+
req.add_header("Content-Type", self.headers.get("Content-Type", "application/json"))
27+
try:
28+
with urllib.request.urlopen(req, timeout=180) as r:
29+
self.send_response(r.status)
30+
self.send_header("Content-Type", r.headers.get("Content-Type", "application/json"))
31+
self.end_headers()
32+
while True:
33+
chunk = r.read(1024)
34+
if not chunk:
35+
break
36+
self.wfile.write(chunk)
37+
self.wfile.flush()
38+
except urllib.error.HTTPError as e:
39+
body = e.read()
40+
self.send_response(e.code)
41+
self.send_header("Content-Type", "application/json")
42+
self.send_header("Content-Length", str(len(body)))
43+
self.end_headers()
44+
self.wfile.write(body)
45+
46+
def do_GET(self) -> None:
47+
if self.path.startswith(("/health", "/papers", "/figures", "/pages/")):
48+
return self._proxy()
49+
super().do_GET()
50+
51+
def do_POST(self) -> None:
52+
if self.path.startswith(("/query", "/demo/chat")):
53+
return self._proxy()
54+
self.send_error(404)
55+
56+
57+
http.server.ThreadingHTTPServer(
58+
("127.0.0.1", 8765), functools.partial(H, directory=ROOT)
59+
).serve_forever()

0 commit comments

Comments
 (0)