mirror of
https://github.com/kennethreitz/rhymepad.org.git
synced 2026-10-24 07:19:12 +00:00
The shell picks up what the new framework offers: validated query params (limit=abc is a 422 now, not a 500), a 512KB body cap ahead of the JSON parser, per-IP rate limits on the PIL-rendering og card and the editor endpoints, security headers and auto-ETags on everything, a 60s request budget, orjson under the JSON, and lifespan instead of on_event. Handlers return their responses now instead of mutating resp — except where RateLimiter.limit would swallow the return value (upstream bug; check() is called in-handler instead). Plus a web-layer test suite and a verify skill, and the README finally serves granian. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
44 lines
1.8 KiB
Markdown
44 lines
1.8 KiB
Markdown
---
|
||
name: verify
|
||
description: Launch and drive rhymepad.org end-to-end to verify a change against the real HTTP surface.
|
||
---
|
||
|
||
# Verifying rhymepad.org
|
||
|
||
The surface is HTTP: a Responder app (`app:api`) served by granian.
|
||
|
||
## Launch
|
||
|
||
```bash
|
||
uv run granian --interface asgi --host 127.0.0.1 --port 8791 app:api
|
||
```
|
||
|
||
Run it in the background. Boot includes `rhymes.warm()` (g2p model +
|
||
NLTK data) via the lifespan hook — poll `/healthz` for up to ~2 min
|
||
before declaring it dead. NLTK data missing locally? `make nltk-data`.
|
||
|
||
## Flows worth driving
|
||
|
||
- `GET /healthz` → `{"ok": true}` — also carries the global headers
|
||
(security headers, ETag), so it doubles as a header check.
|
||
- `POST /api/analyze` with `{"text": "cat in a hat\nbat on a mat"}` →
|
||
tokens/groups JSON. Body > 512 KB → 413 (framework cap);
|
||
`text` > `rhymes.MAX_DRAFT` (100k chars) → 413 with detail.
|
||
- `GET /api/lookup?word=cat&limit=5` — `limit` is a validated Query
|
||
param: non-int or outside 1–500 → 422 problem-details JSON.
|
||
- `GET /api/og?d=<payload>` → PNG + immutable Cache-Control. Build a
|
||
payload: `base64.urlsafe_b64encode(gzip.compress(json.dumps({"x":
|
||
"..."}).encode()))`, strip `=`. Garbage `d` → 404 "bad draft link".
|
||
Rate-limited 30/min per client IP (X-Forwarded-For when present) —
|
||
31 rapid hits show the 429.
|
||
- `GET /?d=<payload>` → title swapped into `<title>… · RhymePad</title>`
|
||
plus `noindex` robots meta.
|
||
- Conditional GET: take any ETag, re-request with `If-None-Match` → 304.
|
||
|
||
## Gotchas
|
||
|
||
- Shell is fish; multi-line loops in Bash tool calls need `bash -c`.
|
||
- The rate limiter's memory backend persists per-process — restarting
|
||
granian resets budgets; repeated verify runs against one process eat
|
||
the og budget (send a fresh `X-Forwarded-For` to get a clean one).
|