Rift Extensions (_rift Namespace)
Rift extends Mountebank’s JSON configuration with advanced features through the _rift namespace. This allows you to use Mountebank-compatible configurations while adding Rift-specific capabilities.
Overview
The _rift namespace can be used at two levels:
- Imposter level (
_rift): For imposter-wide settings like flow state - Response level (
_rift): For response-specific features like fault injection and scripting
Flow State
Enable stateful testing scenarios with flow state:
{
"port": 4545,
"protocol": "http",
"_rift": {
"flowState": {
"backend": "inmemory",
"ttlSeconds": 300
}
},
"stubs": [{
"responses": [{
"inject": "function(request, state) { state.count = (state.count || 0) + 1; return { statusCode: 200, body: 'Count: ' + state.count }; }"
}]
}]
}
Flow State Backends
| Backend | Description | Use Case |
|---|---|---|
inmemory | In-process storage (default) | Single instance, testing |
redis | Redis-backed distributed storage | Multi-instance, production |
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
backend | string | "inmemory" | Storage backend: inmemory or redis |
ttlSeconds | integer | 300 | Time-to-live for state entries (5 minutes) |
redis | object | - | Redis-specific configuration (required for redis backend) |
Fail-loud backend errors: an unknown backend string, or a redis backend that can’t be created (missing redis config, a connection/pool failure, or a binary built without the redis-backend feature), fails imposter creation with 400 Bad Request rather than silently degrading to a no-op store. See Flow State → Backend configuration is fail-loud for details. Only the implicit case — no flowState block at all — stays silent, using a no-op store.
Redis Configuration
When using redis backend:
"_rift": {
"flowState": {
"backend": "redis",
"ttlSeconds": 600,
"redis": {
"url": "redis://localhost:6379",
"poolSize": 10,
"keyPrefix": "rift:"
}
}
}
| Option | Type | Default | Description |
|---|---|---|---|
url | string | required | Redis connection URL |
poolSize | integer | 10 | Connection pool size |
keyPrefix | string | "rift:" | Prefix for all keys (namespace isolation) |
Connection URL formats:
# Basic
redis://localhost:6379
# With password
redis://:password@localhost:6379
# With database selection
redis://localhost:6379/0
# TLS connection
rediss://localhost:6379
# Sentinel
redis+sentinel://localhost:26379/mymaster
Key isolation example:
{
"flowState": {
"backend": "redis",
"redis": {
"url": "redis://localhost:6379",
"keyPrefix": "rift:staging:"
}
}
}
This prefixes all keys with rift:staging: to isolate test environments.
Enabling Redis Backend
Redis support requires building with the redis-backend feature:
# Build with Redis support
cargo build --release --features redis-backend
# Run with Redis backend
rift-http-proxy --configfile imposters.json
Imposter Settings
Besides flowState, the imposter-level _rift block accepts these settings:
metrics
Per-imposter metric emission.
"_rift": {
"metrics": { "enabled": false, "port": 9090 }
}
| Field | Type | Default |
|---|---|---|
enabled | boolean | false |
port | integer | 9090 |
proxy
Upstream target and connection-pool tuning for proxy responses.
"_rift": {
"proxy": {
"upstream": { "host": "api.example.com", "port": 443, "protocol": "https" },
"connectionPool": { "maxIdlePerHost": 100, "idleTimeoutSecs": 90 }
}
}
| Field | Type | Default |
|---|---|---|
upstream.host | string | — |
upstream.port | integer | — |
upstream.protocol | string | "http" |
connectionPool.maxIdlePerHost | integer | 100 |
connectionPool.idleTimeoutSecs | integer | 90 |
scriptEngine
Defaults for _rift.script execution.
"_rift": {
"scriptEngine": { "defaultEngine": "rhai", "timeoutMs": 5000 }
}
| Field | Type | Default | Notes |
|---|---|---|---|
defaultEngine | string | "rhai" | Engine used when a script omits engine. |
timeoutMs | integer | 5000 | Per-script wall-clock timeout. |
Strict Behaviors (strictBehaviors)
strictBehaviors is a top-level imposter field — a sibling of protocol/stubs, not nested under _rift — that controls whether a failing response behavior degrades quietly or fails loudly.
| Field | Type | Default | Description |
|---|---|---|---|
strictBehaviors | boolean | false | When true, a decorate/shellTransform/binary-base64-decode failure returns 500 instead of silently serving the fallback body. |
{
"port": 4545,
"protocol": "http",
"strictBehaviors": true,
"stubs": [{
"responses": [{
"is": {"statusCode": 200, "body": "Hello"},
"_behaviors": {"decorate": "function(request, response) { throw new Error('boom'); }"}
}]
}]
}
By default (strictBehaviors: false), a throwing decorate/shellTransform, or a _mode: "binary" response whose body isn’t valid base64, still serves the stub’s fallback body and only signals the failure via an x-rift-<behavior>-error header. With strictBehaviors on, the same failure returns 500 (still carrying that header) instead.
Strict mode can also be forced process-wide with the RIFT_STRICT_BEHAVIORS environment variable (see CLI Reference) — the per-imposter flag or the env var enables it.
See Mountebank Behaviors → Error Semantics for the full walkthrough of decorate/shellTransform/binary failures under strict mode.
Route Patterns (routePattern)
routePattern is a top-level stub field — a sibling of predicates/responses/id/ scenarioName, not nested under _rift — that declares a path shape (e.g. /users/:id) for extracting named path parameters from the request.
| Field | Type | Default | Description |
|---|---|---|---|
routePattern | string | — | Path shape with :name segments to capture as path parameters. |
When the request path has the same number of /-separated segments as the pattern, each literal segment must match exactly and each :name segment captures the corresponding path segment. The captured values are exposed as:
${request.pathParams.<name>}in response templates (see Request Interpolation).request.pathParams.<name>in scripts, for every engine (see Scripting).
If routePattern is absent, or the request path doesn’t match its segment shape, pathParams is simply empty — nothing errors. Extraction is :name-segment matching only; there’s no regex or glob support.
{
"routePattern": "/users/:id",
"predicates": [{ "matches": { "path": "^/users/[^/]+$" } }],
"responses": [{ "is": { "statusCode": 200, "body": "user ${request.pathParams.id}" } }]
}
Fault Injection
Add probabilistic fault injection to responses:
Latency Faults
{
"is": {"statusCode": 200, "body": "OK"},
"_rift": {
"fault": {
"latency": {
"probability": 0.3,
"minMs": 100,
"maxMs": 500
}
}
}
}
Or with fixed delay:
"_rift": {
"fault": {
"latency": {
"probability": 1.0,
"ms": 200
}
}
}
Error Faults
{
"is": {"statusCode": 200, "body": "OK"},
"_rift": {
"fault": {
"error": {
"probability": 0.1,
"status": 503,
"body": "Service Unavailable",
"headers": {
"Retry-After": "60"
}
}
}
}
}
TCP Faults
tcp is a string naming a connection-level fault (not an object). When it fires, the connection is disrupted at the transport level instead of an HTTP response being sent:
"_rift": {
"fault": {
"tcp": "CONNECTION_RESET_BY_PEER"
}
}
TCP fault types (canonical name or short alias):
| Value | Aliases | Effect |
|---|---|---|
CONNECTION_RESET_BY_PEER | reset | Real TCP reset (RST) |
EMPTY_RESPONSE | empty | Close with no bytes sent |
RANDOM_DATA_THEN_CLOSE | random, garbage | Write random bytes, then close |
MALFORMED_RESPONSE_CHUNK | malformed | Status line + malformed chunked body, then close |
When latency, tcp, and error are combined in one fault block, tcp takes precedence over error. See Fault Injection for precedence, the top-level fault response form, and scripted faults.
Scripting
_rift.script runs a script (engine rhai or javascript) that decides whether to inject a response. The script defines respond(ctx) and returns a result constructor — http(status, body), delay(ms), reset(), or pass()/nothing for no injection. ctx.state is a key/value handle already scoped to the request’s resolved flow id — no explicit flow id argument needed. See Scripting for the full ctx reference.
{
"_rift": {
"flowState": { "backend": "inmemory", "ttlSeconds": 300 },
"script": {
"engine": "rhai",
"code": "fn respond(ctx) { let n = ctx.state.incr(\"count\"); http(200, `count ${n}`) }"
}
}
}
rhaiis built in;javascriptrequires thejavascriptfeature. JavaScript can also use the Mountebankinjectresponse format directly.- Scripts require
--allow-injectionand are bounded by a wall-clock timeout (_rift.scriptEngine.timeoutMs, default 5000 ms).
See Scripting for the full API (request object, flow_store methods, last_error(), return values) and Flow State for the state model.
Complete Example
{
"port": 4545,
"protocol": "http",
"_rift": {
"flowState": {
"backend": "inmemory",
"ttlSeconds": 300
}
},
"stubs": [
{
"predicates": [{"equals": {"path": "/api/users"}}],
"responses": [{
"is": {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "{\"users\": []}"
},
"_rift": {
"fault": {
"latency": {
"probability": 0.2,
"minMs": 50,
"maxMs": 200
}
}
}
}]
},
{
"predicates": [{"equals": {"method": "POST", "path": "/api/orders"}}],
"responses": [{
"is": {"statusCode": 201, "body": "Created"},
"_rift": {
"fault": {
"error": {
"probability": 0.05,
"status": 503,
"body": "Service temporarily unavailable"
}
}
}
}]
},
{
"predicates": [{"equals": {"path": "/api/counter"}}],
"responses": [{
"_rift": {
"script": {
"engine": "rhai",
"code": "let count = flow.get('requests').unwrap_or(0) + 1; flow.set('requests', count); #{ statusCode: 200, body: `Request #${count}` }"
}
}
}]
}
]
}
Combining with Mountebank Features
_rift extensions work alongside standard Mountebank features:
{
"is": {
"statusCode": 200,
"body": "Hello"
},
"_behaviors": {
"wait": 50,
"decorate": "function(request, response) { response.body += ' World'; }"
},
"_rift": {
"fault": {
"latency": {
"probability": 0.1,
"ms": 100
}
}
}
}
Both _behaviors.wait and _rift.fault.latency will be applied.
See Also
- Mountebank Compatibility - Standard Mountebank configuration
- Fault Injection - Detailed fault injection documentation
- Scripting - Scripting engine documentation