# Phase 9: HTTP Server

## Status: Complete

## Goal
HTTP API for publish and queue info queries. Simpler path for clients that don't need persistent subscriptions.

## Files
- `src/server/http.rs` — `HttpServer` struct, `AppState`, router construction, graceful shutdown
- `src/server/http/error.rs` — `HttpError` type, `Error` → HTTP status/JSON mapping, error code names
- `src/server/http/routes.rs` — handler functions for all four endpoints
- `src/server/http/middleware.rs` — `authenticate()`, `verify_namespace()`, `extract_bearer_token()` helpers
- `tests/http_integration.rs` — 17 integration tests
- `tests/common/mod.rs` — `TestHttpServer`, `start_test_http_server` helpers
- `docs/api.md` — full endpoint documentation

## Work Items

### server/http.rs
- [x] `HttpServer<M>` struct — holds config, `AppState` (manager, auth, message ID counter), shutdown receiver
- [x] `async fn run(&self) -> Result<()>` — bind to configured address/port, serve with graceful shutdown
- [x] `async fn run_with_listener(&self, listener)` — for tests binding to port 0
- [x] Share state with handlers via axum `State` extractor
- [x] Message ID counter is injectable (`Arc<AtomicU64>`) so TCP and HTTP servers share one counter

### server/http/error.rs
- [x] `HttpError` struct with status code, `ErrorCode`, and message
- [x] `IntoResponse` impl renders JSON `{ "error": { "code", "name", "message" } }`
- [x] `From<Error>` maps domain errors to HTTP status codes (401, 403, 404, 503, 500)
- [x] `error_code_name()` maps `ErrorCode` to machine-readable strings (shared with TCP)

### server/http/routes.rs
- [x] `POST /v1/{namespace}/{topic}/publish` — raw bytes body, returns `{ offset, message_id }`
- [x] `GET /v1/{namespace}/{topic}/info` — returns depth, bytes, offsets, subscriber/group counts
- [x] `GET /v1/{namespace}/topics` — returns sorted list of topic names
- [x] `GET /health` — returns 200 OK, no auth required

### server/http/middleware.rs
- [x] `extract_bearer_token()` — parses `Authorization: Bearer <key>` header
- [x] `authenticate()` — validates key via `Authenticator`, returns `AuthSession`
- [x] `verify_namespace()` — checks session namespace matches URL path namespace
- [x] Request logging via tracing in `authenticate()`

### docs/api.md
- [x] Document each endpoint: method, path, headers, request/response body, status codes
- [x] Error response format with code/name/message structure
- [x] Error code table mapping codes to HTTP status
- [x] Caveat: `subscriber_count`/`consumer_group_count` reflect TCP subscribers only

## Trait extensions (supporting changes)

### QueueEngine trait (`src/queue.rs`)
- [x] Added `list_topics(&self, ns) -> Vec<TopicName>` — list topic names in a namespace
- [x] Added `topic_stats(&self, ns, topic) -> Option<TopicStats>` — moved from inherent method to trait

### NamespaceManager trait (`src/namespace.rs`)
- [x] Added `list_topics(&self, ns) -> Result<Vec<TopicName>>` — merges engine topics + subscriber topics
- [x] Added `topic_info(&self, ns, topic) -> Result<TopicInfo>` — combines queue stats + subscriber counts

### Types (`src/types.rs`)
- [x] Added `TopicInfo` struct — depth, bytes_used, base/head offset, subscriber_count, consumer_group_count

## Key Decisions
- Axum 0.8 for HTTP (already a dependency, ergonomic with tokio)
- JSON responses for info/error, raw bytes for publish payload
- API versioning via URL prefix `/v1/`
- HTTP is publish + query only — no subscribe (that's TCP's job)
- Auth via helper functions called in each handler, not an axum middleware layer — simpler with only 3 protected routes, keeps auth logic visible at each call site
- `HttpError` in its own `error.rs` module — shared by routes and middleware
- Message ID counter injected from caller so TCP + HTTP share a single counter
- `list_topics` merges both queue engine topics (have data) and namespace state topics (have subscribers), sorted alphabetically

## Integration Tests (17)

| Test | What it verifies |
|------|-----------------|
| `health_returns_200` | Health endpoint works without auth |
| `publish_returns_offset_and_message_id` | Publish returns correct JSON response |
| `publish_message_lands_in_queue_engine` | Published message readable from engine |
| `publish_sequential_offsets` | Sequential publishes get offsets 0, 1, 2, ... |
| `publish_without_auth_returns_401` | Missing Authorization header → 401 |
| `publish_with_bad_key_returns_401` | Invalid API key → 401 |
| `publish_without_publish_permission_returns_403` | Key lacks Publish perm → 403 |
| `query_without_query_permission_returns_403` | Key lacks Query perm → 403 |
| `topic_info_returns_correct_depth` | Info endpoint returns correct depth/offsets |
| `topic_info_nonexistent_returns_404` | Info on missing topic → 404 |
| `list_topics_returns_published_topics` | Lists topics that have data |
| `list_topics_empty_namespace` | Empty namespace returns empty list |
| `publish_to_wrong_namespace_returns_401` | Key namespace ≠ URL namespace → 401 |
| `publish_empty_body` | Empty payload publishes and stores correctly |
| `publish_large_payload` | 100KB payload round-trips correctly |
| `concurrent_publishes_get_unique_offsets` | 50 concurrent clients get unique sequential offsets |
| `error_response_has_correct_json_structure` | Validates error.code/name/message across 4 error types |

## Future Changes

### Multi-Key Auth (Plan 4 Phase 4.1)

When Plan 4 Phase 4.1 (programmatic API key management) lands, the HTTP auth middleware changes minimally:

- `authenticate()` already calls `Authenticator::authenticate(key)`, which does a linear scan of all `KeyEntry` entries. With multiple keys per namespace, the scan naturally matches any valid key for any namespace — **no middleware code change required**.
- `verify_namespace()` compares the `AuthSession.namespace` against the URL path namespace. Since `AuthSession` is returned by `authenticate()` with the correct namespace for whichever key matched, this also works unchanged.
- The only visible difference: multiple different API keys can now successfully authenticate to the same namespace, each with potentially different permissions.

No changes to `src/server/http/middleware.rs` or `src/server/http/routes.rs` are required.

## Open Questions
- Rate limiting? Probably v2.
- CORS? Only if there's a browser client use case.
- Batch publish endpoint?
