chatbot1-8 Exercise 1: Talking to the Web Channel From Code ============================================================= Start the demo server in one terminal: py serve_web.py 8765 (It listens on 127.0.0.1 only. Stop it with Ctrl+C, or by its process id: do not kill every Python process.) ex1.py (save in the project root, run it in another terminal): """Run serve_web.py first (py serve_web.py 8765), then run this in another terminal.""" import json import httpx BASE = "http://127.0.0.1:8765" with httpx.Client(base_url=BASE) as client: client.get("/") # the page sets the session cookie print("cookie held:", list(client.cookies.keys())) r = client.post("/api/chat", json={"message": "hello"}) # no Origin header print("without Origin ->", r.status_code, r.json()) with client.stream("POST", "/api/chat", json={"message": "/remember I like tea"}, headers={"Origin": BASE}) as r: print("with Origin ->", r.status_code, r.headers["content-type"]) for line in r.iter_lines(): if line.startswith("data: "): print(" event:", json.loads(line[6:])) with httpx.Client(base_url=BASE) as stranger: # a second browser, with no cookie at all print("no cookie ->", stranger.post("/api/chat", json={"message": "hi"}, headers={"Origin": BASE}).status_code) Run with: py ex1.py Output (from a real run against the running server): cookie held: ['chat_sid'] without Origin -> 403 {'detail': 'cross-origin request refused'} with Origin -> 200 text/event-stream; charset=utf-8 event: {'type': 'final', 'text': 'Remembered as #1.', 'changed': False} no cookie -> 401 The four outcomes: 1. Loading the page set the session cookie (chat_sid). 2. POST without an Origin header: 403 "cross-origin request refused". Neither Origin nor Sec-Fetch-Site showed a trusted source, so the request is refused. 3. POST with Origin equal to the site's own origin: 200 and a text/event-stream. The command /remember is answered by a single final event; no model was called. 4. A client with no cookie: 401. It must load the page first. WHY THIS WORKS AS AN ANSWER --------------------------- httpx.Client keeps cookies between calls, so after the GET the client behaves like a browser that has visited the page. The endpoint requires a valid signed cookie (401 otherwise), a trusted origin (403 otherwise) and a JSON content type (415 otherwise); only then does it stream. A script is not a browser, so it has to supply the Origin header itself, which is exactly what a forged cross-site request from a real browser cannot do: a browser sets Origin to the attacking site, and it is not on the list.