Hot Reload
POST /admin/reload re-reads the startup config source and applies it incrementally — Rift diffs the running imposters against the new config and touches only what actually changed. Editing an imposter in a file and reloading no longer tears every imposter down.
Requirements & behavior
- Rift must have been started with a config source:
--configfile <file>or--datadir <dir>. Without one, reload is a no-op that returns200. - The new config is validated in full before any running imposter is mutated. If it fails to parse or has duplicate ports / unsupported protocols, the running imposters are left untouched and the call errors.
- The reload is incremental (issue #319): each port is diffed and only the delta is applied. Unchanged imposters — and unchanged stubs within a changed imposter — keep their runtime state: recorded requests, scenario state, and response cyclers (
repeat) all survive the reload.
rift --configfile ./imposters.json # start with a config source
# ...edit imposters.json...
curl -X POST http://localhost:2525/admin/reload # 200; delta applied, state preserved
To reload from a directory of one-imposter-per-file configs, start with --datadir ./mb-data instead; POST /admin/reload re-reads the directory.
What the diff does
Rift computes the change set per port and classifies each imposter:
- created — a port present in the new config but not running.
- deleted — a running port absent from the new config.
- replaced — an imposter whose imposter-level fields changed, or whose stub set changed so substantially (more than ~50% of stubs) that an in-place patch is not worthwhile. A replaced imposter starts with fresh runtime state.
- stubPatched — an imposter whose stubs changed only modestly; the differing stubs are patched in place and every unchanged stub keeps its cursor/scenario state.
Stubs are matched across a reload by a stable key: a stub’s explicit id if it has one, otherwise a content hash. Reordering stubs or editing a neighbour therefore preserves the state of the stubs you didn’t touch.
Reload response
A successful reload returns 200 with the change set:
{
"message": "Reloaded 3 imposter(s)",
"created": [4547],
"replaced": [4545],
"stubPatched": [4546],
"deleted": [4544]
}
Reload applies imposters only. If the config file also declares an intercept block, it is not re-applied — re-seeding would duplicate or clobber rules added at runtime, and rebinding the listener is a lifecycle change reload does not own. So that an edit to the block never looks applied, the response says so explicitly (the field is absent otherwise):
{
"message": "Reloaded 3 imposter(s)",
"warnings": [
"the config file's `intercept` block is applied at startup only and was NOT re-applied; ..."
],
"created": [], "replaced": [], "stubPatched": [], "deleted": []
}
Change intercept rules at runtime with POST/DELETE /intercept/rules, or restart to re-read the block.
If some ports apply and others fail, the call returns 500 and reports both sides — the ports that did apply and the ones that failed:
{
"errors": [{ "code": "500", "message": "Reload partially failed: ..." }],
"failed": ["4545: ..."],
"created": [],
"replaced": [],
"stubPatched": [],
"deleted": []
}
A validation failure that is caught before any mutation returns 500 with an errors array and leaves every running imposter in place.
Embedders can also observe the diff programmatically: an incremental apply emits imposter change events (
Created/Replaced/StubsChanged/Deleted) to any registered listener.