83 lines
4.1 KiB
Markdown
83 lines
4.1 KiB
Markdown
# M3.5.1 — HTTP server + router, Kong auth hook, metrics
|
||||
|
|
|
|||
|
|
| Field | Value |
|
|||
|
|
|---|---|
|
|||
|
|
| Phase | M3.5 — Distributed API Layer |
|
|||
|
|
| Size | M — 1–3 days |
|
|||
|
|
| Status | ⬜ Not started |
|
|||
|
|
| Flags | — |
|
|||
|
|
| Spec | inlined below |
|
|||
|
|
| Blocks | M3.5.2, M3.5.3, M3.5.5, M3.5.6 |
|
|||
|
|
|
|||
|
|
## Goal
|
|||
|
|
|
|||
|
|
HTTP facade for homelab gateway. Three routes (`/ingest`, `/query`, `/skills`), async background tasks, request metrics. Auth hook validates Kong `apikey:` header. Stateless — no business logic here, just request demultiplexing.
|
|||
|
|
|
|||
|
|
## Architecture
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Kong (api.riotpiao.com)
|
|||
|
|
↓ apikey validation
|
|||
|
|
HTTP Server (Rust httpd, actix-web or axum)
|
|||
|
|
↓ route dispatch
|
|||
|
|
/ingest (async) /query (sync) /skills (read-only)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Steps
|
|||
|
|
|
|||
|
|
1. `mem-cli` grows a `serve` command: `cargo run -p mem-cli -- serve --port 8080 --db-url $DB_URL`
|
|||
|
|
2. Choose framework: **actix-web** (stable, high perf) or **axum** (newer, composable). Decision required — pick one and document the choice.
|
|||
|
|
3. Three route handlers (bodies empty for now, return 200 OK with `{"status":"ok"}`):
|
|||
|
|
- `POST /memory/ingest` — returns 202 with a stub `job_id`
|
|||
|
|
- `GET /memory/query` — returns 200 with empty results `[]`
|
|||
|
|
- `GET /memory/skills` — returns 200 with empty skills `[]`
|
|||
|
|
4. Request logger middleware — every request logs method, path, status, latency in one line (not pretty-printed).
|
|||
|
|
5. Metrics middleware — track latency histogram per route (p50/p95/p99 in microseconds), request count, error count.
|
|||
|
|
6. Kong auth hook:
|
|||
|
|
- Extract `apikey:` header (case-insensitive header name, exact value match against stored key)
|
|||
|
|
- If missing or unrecognized → 401 with `{"error":"unauthorized","reason":"missing apikey header"}`
|
|||
|
|
- Pass apikey to request context so handlers can log which key made the request
|
|||
|
|
7. CORS: disable (agents are internal cluster; no browser requests expected)
|
|||
|
|
8. Health check: `GET /health` returns 200 `{"status":"ok","uptime_seconds":N}`
|
|||
|
|
|
|||
|
|
## Acceptance
|
|||
|
|
|
|||
|
|
- Server starts without errors
|
|||
|
|
- Health check responds
|
|||
|
|
- Three routes defined and callable
|
|||
|
|
- Auth middleware rejects missing apikey (401)
|
|||
|
|
- Request logger emits latency per request
|
|||
|
|
- Metrics collected (observable via endpoint or in-process)
|
|||
|
|
|
|||
|
|
## Verify
|
|||
|
|
|
|||
|
|
**Harness:** Integration tests against a live server instance started in each test.
|
|||
|
|
|
|||
|
|
**Integration test** — `tests/it_http_server.rs`:
|
|||
|
|
1. `a1_server_starts` — `HttpServer::new(...).run()` succeeds, port is open.
|
|||
|
|
2. `a2_health_check` — GET /health returns 200 and body contains `"ok"`.
|
|||
|
|
3. `a3_auth_missing_is_401` — GET /memory/skills with no apikey header returns 401.
|
|||
|
|
4. `a4_auth_wrong_is_401` — GET /memory/skills with `apikey: wrong` returns 401.
|
|||
|
|
5. `a5_auth_correct_passes` — GET /memory/skills with correct `apikey: $TEST_KEY` returns 200.
|
|||
|
|
6. `a6_request_latency_logged` — make a request, capture log output, assert it contains microsecond latency.
|
|||
|
|
7. `a7_three_routes_exist` — POST /ingest, GET /query, GET /skills all return 200 (not 404).
|
|||
|
|
8. `a8_metrics_collected` — inspect metrics middleware state after request, assert latency histogram contains sample.
|
|||
|
|
|
|||
|
|
**Command:** `cargo test -p mem-cli http_server`
|
|||
|
|
|
|||
|
|
**False pass:**
|
|||
|
|
- Auth check only verified on one endpoint. Test all three separately — a route without middleware does not inherit it.
|
|||
|
|
- Metrics collected but never asserted. A metrics middleware that silently fails still compiles.
|
|||
|
|
- Latency logged in milliseconds. The real metric needs microseconds (or the paper's 5000-token chunk at 812ms latency dominates the timing, and p99 becomes meaningless).
|
|||
|
|
|
|||
|
|
## Traps
|
|||
|
|
|
|||
|
|
- Actix-web's `.service()` does not inherit middleware registered outside a scope; scope middleware applies only to routes inside that scope.
|
|||
|
|
- Header name case matters for Kong's key-auth; `apikey:` is lowercase.
|
|||
|
|
- `tokio::runtime::Runtime::new()` in tests blocks on network if used naively — use test utilities from `actix-web` or `axum` that spawn the server in a background thread.
|
|||
|
|
- Metrics registered at startup are easy to forget to increment. Middleware must actually call the metrics update, not just define it.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
Background: [DESIGN.md § Distributed API Layer](../DESIGN.md#distributed-api-layer-homelab-frontend)
|