feat: SSE endpoint and server mode system (#38, #39) #40

Merged
forbes merged 2 commits from feat-38-39-sse-server-mode into main 2026-02-08 22:00:12 +00:00
Owner

Summary

Adds two tightly coupled features:

  1. Server-Sent Events (GET /api/events) — live mutation notifications for connected clients
  2. Server modenormal, read-only, degraded modes exposed via health endpoints and SSE

New Components

SSE Broker (broker.go)

  • In-process event hub managing connected clients via buffered channels (cap 64)
  • Non-blocking fan-out — slow clients get events dropped, not blocked
  • Ring buffer history (256 events) for Last-Event-ID reconnection replay
  • 30s heartbeat to keep connections alive through proxies
  • Monotonic event IDs via atomic.Uint64

Server State (servermode.go)

  • Mode state machine: normalread-only (explicit) / degraded (MinIO unreachable)
  • Periodic MinIO health check every 30s via new Storage.Ping() method
  • SIGUSR1 toggles read-only mode at runtime
  • read_only: true config option for startup
  • Mode transitions broadcast as server.state SSE events

SSE Handler (sse_handler.go)

  • Uses http.ResponseController.SetWriteDeadline(time.Time{}) to disable the 15s WriteTimeout for SSE connections only
  • Sets X-Accel-Buffering: no for nginx compatibility
  • Replays missed events from Last-Event-ID header
  • Sends initial server.state event on connect

Event Types

Event Trigger
item.created CreateItem, CSV/ODS import
item.updated UpdateItem
item.deleted DeleteItem
revision.created CreateRevision, RollbackRevision
server.state Mode transitions, initial connect
heartbeat Every 30s

Other Changes

  • /health: now returns mode field
  • /ready: now returns mode, sse_clients, and real MinIO connectivity check
  • RequireWritable middleware: returns 503 on all write endpoints when read-only
  • 13 unit tests for broker and server state

Closes #38, closes #39

## Summary Adds two tightly coupled features: 1. **Server-Sent Events** (`GET /api/events`) — live mutation notifications for connected clients 2. **Server mode** — `normal`, `read-only`, `degraded` modes exposed via health endpoints and SSE ## New Components ### SSE Broker (`broker.go`) - In-process event hub managing connected clients via buffered channels (cap 64) - Non-blocking fan-out — slow clients get events dropped, not blocked - Ring buffer history (256 events) for `Last-Event-ID` reconnection replay - 30s heartbeat to keep connections alive through proxies - Monotonic event IDs via `atomic.Uint64` ### Server State (`servermode.go`) - Mode state machine: `normal` → `read-only` (explicit) / `degraded` (MinIO unreachable) - Periodic MinIO health check every 30s via new `Storage.Ping()` method - `SIGUSR1` toggles read-only mode at runtime - `read_only: true` config option for startup - Mode transitions broadcast as `server.state` SSE events ### SSE Handler (`sse_handler.go`) - Uses `http.ResponseController.SetWriteDeadline(time.Time{})` to disable the 15s WriteTimeout for SSE connections only - Sets `X-Accel-Buffering: no` for nginx compatibility - Replays missed events from `Last-Event-ID` header - Sends initial `server.state` event on connect ## Event Types | Event | Trigger | |-------|---------| | `item.created` | CreateItem, CSV/ODS import | | `item.updated` | UpdateItem | | `item.deleted` | DeleteItem | | `revision.created` | CreateRevision, RollbackRevision | | `server.state` | Mode transitions, initial connect | | `heartbeat` | Every 30s | ## Other Changes - **`/health`**: now returns `mode` field - **`/ready`**: now returns `mode`, `sse_clients`, and real MinIO connectivity check - **`RequireWritable` middleware**: returns 503 on all write endpoints when read-only - **13 unit tests** for broker and server state Closes #38, closes #39
forbes added 1 commit 2026-02-08 21:59:48 +00:00
Add server-sent events at GET /api/events for live mutation
notifications. Add server mode (normal/read-only/degraded) exposed
via /health, /ready, and SSE server.state events.

New files:
- broker.go: SSE event hub with client management, non-blocking
  fan-out, ring buffer history for Last-Event-ID replay, heartbeat
- servermode.go: mode state machine with periodic MinIO health
  check and SIGUSR1 read-only toggle
- sse_handler.go: HTTP handler using http.Flusher and
  ResponseController to disable WriteTimeout for long-lived SSE
- broker_test.go, servermode_test.go: 13 unit tests

Modified:
- handlers.go: Server struct gains broker/serverState fields,
  Health/Ready include mode and sse_clients, write handlers
  emit item.created/updated/deleted and revision.created events
- routes.go: register GET /api/events, add RequireWritable
  middleware to all 8 editor-gated route groups
- middleware.go: RequireWritable returns 503 in read-only mode
- csv.go, ods.go: emit bulk item.created events after import
- storage.go: add Ping() method for health checks
- config.go: add ReadOnly field to ServerConfig
- main.go: create broker/state, start background goroutines,
  SIGUSR1 handler, graceful shutdown sequence

Closes #38, closes #39
forbes added 1 commit 2026-02-08 22:00:03 +00:00
forbes merged commit 741df1a1ba into main 2026-02-08 22:00:12 +00:00
forbes deleted branch feat-38-39-sse-server-mode 2026-02-08 22:00:18 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/silo#40