Architecture
featherbit runs three concerns inside one process: the data plane (the HTTP listener that serves client traffic), the admin API (a separate axum server with the embedded UI), and the hot-reload watcher. All three share one state object.
Request flow
HTTP request
→ server::listener matches a route (first match wins, in config order)
→ builds a Context from the request
→ CompiledGraph::execute() walks the policy's nodes
following success/error edges
→ the final Context.response is sent to the client
In detail:
- The data-plane server buffers the request body and scans the route table for the first route whose match rule (path, method, headers, host) accepts the request. Unmatched requests get a JSON 404.
- A fresh Context is built:
requestpopulated from the incoming request,responseempty (status_code0),messageanderrorsempty. - The route's compiled graph executes: starting at the entry node, each plugin runs and the walk follows its success edge on
Okor its error edge (or the catch-all handler) on failure, until a terminalclientnode is reached. See Policies and graphs and Error handling. - The resulting
Context.responseis converted to an HTTP response. Astatus_codeof0(never set by any node) is treated as200.
Shared state and locking
SharedState is wrapped in an Arc and cloned into every server task. It holds:
| Field | Contents |
|---|---|
system | Immutable system.yaml config, fixed for the process lifetime |
gateway | Current gateway.yaml config behind a RwLock, mutated by admin API CRUD |
routes | RwLock-protected route table: each route paired with an Arc<CompiledGraph> of its policy, in declaration order |
config_path | Path to gateway.yaml, needed for reload-from-disk |
metrics | Process-wide Prometheus registry |
The locking model keeps the request path cheap:
- The data plane only takes short read locks on
routes, and only while matching the request. The lock is released before graph execution starts, so a slow upstream call never blocks a config reload. - Write locks are taken only by the admin API and the hot-reload paths, when swapping in a freshly compiled route table.
- Route recompilation never happens on the request path.
Policy compilation: validate → compile → swap
Every configuration load follows the same three steps:
- Validate — each policy's graph structure is checked (
validate_policy): listener and client nodes present, edges reference existing nodes, no duplicate inputs, no orphans. All violations are collected and reported together. - Compile — each valid policy is turned into a
CompiledGraphwith instantiated plugin objects and edges indexed by source port. Policies shared by multiple routes are compiled once and shared viaArc. - Swap — the new route table replaces the old one under a write lock.
This runs at startup (the process exits on invalid config — fail fast), after admin API mutations (reload), and when the file watcher or POST /api/config/reload re-reads gateway.yaml from disk (reload_from_disk). A failed reload has no side effects: if parsing, validation, or compilation fails, the existing route table stays in place and traffic keeps flowing on the last good configuration.
Module map
| Module | Responsibility |
|---|---|
src/main.rs | Entry point, CLI flags, startup orchestration, logging init |
src/config/ | YAML loading, ${ENV_VAR:-default} interpolation, config structs |
src/context/ | The Context object (request, response, message, errors) |
src/graph/engine.rs | Compiles PolicyConfig into CompiledGraph; executes the node walk |
src/graph/validation.rs | Structural validation of policy graphs before compilation |
src/routing/ | Path/method/header/host route matching |
src/plugins/native/ | Built-in plugins |
src/plugins/script/ | Lua scripting runtime, Context↔Lua marshalling |
src/server/ | Data-plane HTTP listener, request dispatch |
src/admin/ | Admin API (axum), Basic Auth middleware, embedded UI serving |
src/metrics/ | Prometheus metrics registry |
src/hot_reload/ | File watcher triggering reload on gateway.yaml changes |
src/state.rs | SharedState: lock-protected config and compiled route table |
Configuration files
system.yaml— listener bind/port, timeouts, logging, admin API settings. Loaded once at startup.gateway.yaml— routes and policies. Hot-reloaded on change.
All YAML values in both files support ${ENV_VAR:-default} interpolation, resolved at load time.
TLS termination (with hot-reloaded certs, mTLS, and SNI), HTTP/2, WebSocket proxying, L4 TCP/UDP stream proxying, etcd-backed clustering, and graceful shutdown are all implemented — see the TLS, stream, and deployment guides. The remaining gaps are tracked honestly on the roadmap.