Skip to content

Split Raft::HTTP::Handler into StatusHandler and AdminHandler#28

Merged
antondalgren merged 1 commit into
mainfrom
split-http-handler
Jun 11, 2026
Merged

Split Raft::HTTP::Handler into StatusHandler and AdminHandler#28
antondalgren merged 1 commit into
mainfrom
split-http-handler

Conversation

@antondalgren

Copy link
Copy Markdown
Contributor

Summary

The old `Handler` bundled read-only GETs (`/raft/status`, `/raft/log`, `/raft/metrics`) with mutating `POST /raft/admin/*` in a single handler. Consumers that want to expose metrics on an unauthenticated port were forced to expose unauthenticated cluster-mutating admin endpoints alongside.

Replace with two handlers that can be mounted separately:

  • `StatusHandler(T)` — read-only routes (`GET /raft/status`, `/raft/log`, `/raft/metrics`). Anything else falls through via `call_next`. Safe on an unauthenticated metrics port.
  • `AdminHandler(T)` — `POST /raft/admin/*` including the `raft_debug`-flagged pause/resume/partition/heal/reset routes. Anything else falls through. Mount behind authentication.

No auth is added to the shard itself — auth is consumer policy. The two halves can be combined into one mount with `[StatusHandler, AdminHandler]` to preserve current behaviour where needed.

Behaviour is byte-for-byte identical to the old handler: same JSON shapes, status codes, error messages, and `JSON::ParseException | KeyError | ArgumentError` rescue around admin routes.

Changes

  • `src/raft/http/status_handler.cr` — new, GET-only routes
  • `src/raft/http/admin_handler.cr` — new, POST admin routes
  • `src/raft/http/handler.cr` — deleted, no backwards-compat shim (pre-1.0)
  • `src/raft.cr` — requires both new files
  • `examples/kv/src/main.cr`, `examples/queue/src/main.cr` — chain both handlers (drop-in replacement for the old single handler)
  • `spec/raft/http/{status,admin}_handler_spec.cr` — split tests with new cases:
    • StatusHandler does NOT respond to `POST /raft/admin/...` (falls through; bare chain → 404) — proves the metrics-port mount cannot mutate
    • AdminHandler does NOT respond to `GET /raft/status` (falls through)
    • A chain `[StatusHandler, AdminHandler]` serves both route sets — parity with the old combined handler
    • `raft_debug`-gated pause/resume coverage moved to admin spec

LavinMQ note

LavinMQ is the sole external consumer. It instantiates `Raft::HTTP::Handler` in one place per integration. The upgrade is mechanical: replace the single instantiation with `StatusHandler` + `AdminHandler` mounted on whichever ports their auth chain dictates.

Test plan

  • `crystal spec spec/raft/ -Dpreview_mt -Dexecution_context` — 118/118
  • `crystal spec spec/raft/ -Dpreview_mt -Dexecution_context -Draft_debug` — 125/125
  • `crystal build --no-codegen --error-on-warnings` on lib + both examples — clean
  • `crystal tool format --check src/ spec/` — clean
  • `lib/ameba/bin/ameba --fail-level error src/ spec/` — exit 0 (54 pre-existing warnings, none in new files)

🤖 Generated with Claude Code

The old Handler bundled read-only GETs (/raft/status, /raft/log,
/raft/metrics) with mutating POST /raft/admin/* in a single handler.
This forced consumers that expose metrics on an unauthenticated port
to also expose cluster-mutating admin endpoints there.

Replace with two handlers that can be mounted separately:

- StatusHandler(T): GET /raft/status, /raft/log, /raft/metrics.
  Anything else falls through via call_next. Safe to mount on an
  unauthenticated metrics port.
- AdminHandler(T): POST /raft/admin/*, including the raft_debug-flagged
  pause/resume/partition/heal/reset routes. Anything else falls through.
  Mount behind authentication.

No auth in the shard itself — auth is consumer policy. The two halves
can be combined into one mount with `[StatusHandler, AdminHandler]` to
preserve current behaviour where needed.

Behavior is byte-for-byte identical to the old handler: same JSON
shapes, status codes, error messages, and JSON::ParseException /
KeyError / ArgumentError rescue around admin routes.

Updates:
- src/raft.cr: require both new files instead of the old handler.
- examples/kv/src/main.cr, examples/queue/src/main.cr: mount both
  handlers as a chain (drop-in replacement for the old single handler).
- spec/raft/http/{status,admin}_handler_spec.cr: split tests with new
  cases proving StatusHandler doesn't respond to POST admin (falls
  through to 404), AdminHandler doesn't respond to GET status, and a
  chained mount serves both surfaces.

Delete src/raft/http/handler.cr — pre-1.0, no backwards-compat shim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@antondalgren antondalgren merged commit d825d79 into main Jun 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant