Skip to content

Repository files navigation

Rift

High-performance Mountebank-compatible HTTP/HTTPS mock server written in Rust

StatusReleasecrates.ioDockerLicenseRust

Rift is a Mountebank-compatible mock server written in Rust. Its throughput stays flat as your stub file grows — on an M4 laptop, 211,378 → 209,523 RPS between the first and the last stub of the same 310-stub imposter, where Mountebank falls 8,546 → 1,344 across that same pair — and the same engine embeds in-process in Java, Node, Go and Scala. It loads your existing imposters.json unchanged.

Documentation | Quick Start | Examples

Starting Rift in Docker, creating an imposter over the Mountebank-compatible admin API, and calling it


Why Rift?

Mountebank Compatible

  • Same REST API - Works with existing Mountebank clients and tooling
  • Same Configuration - Load your imposters.json without changes
  • Same Behavior - Predicates, responses, behaviors all work identically

Performance

The slope matters more than the multiple. A mock server's job is to decide which of your stubs matches a request, and the straightforward way to do that is to try each one in turn — so its cost grows with a config file that only ever grows. Rift indexes stubs instead of scanning them. Measured against stub position alone, holding the corpus and the predicate shape fixed: moving from the first stub of a 310-stub imposter to the last costs Rift 0.9% of its throughput and Mountebank 84%. How the matching works.

Throughput at the first, middle and last matching stub of one 310-stub imposter. Rift is flat on both hosts; WireMock falls 69% and Mountebank 84%.

Both engines, same machine, same load — measured on two very different hosts so you can see how much of the gap is the engine and how much is the hardware:

WorkloadApple M4 laptop
Mountebank → Rift
AMD EPYC 9V74, 16 vCPU
Mountebank → Rift
Simple static stub8,898 → 214,818 RPS (24x)5,982 → 324,952 RPS (54x)
Deep path match (310 stubs)1,344 → 209,523 RPS (156x)542 → 322,530 RPS (595x)
Complex AND/OR predicates4,703 → 191,987 RPS (41x)1,814 → 259,548 RPS (143x)
JSON body equals7,611 → 199,670 RPS (26x)2,730 → 294,294 RPS (108x)
JSONPath predicate4,312 → 199,404 RPS (46x)1,921 → 304,796 RPS (159x)
XPath predicate5,542 → 187,869 RPS (34x)1,966 → 247,897 RPS (126x)
Regex path (100 patterns)112 → 207,024 RPS (1,857x)52 → 317,851 RPS (6,160x)

Read the two columns together, not separately. Rift gets faster with more cores (215k → 325k); Mountebank gets slower (8,898 → 5,982), because it is single-threaded and the server's individual cores are slower than the laptop's. So the EPYC multipliers are inflated at both ends — the M4 column is the more conservative read, and it is still 24x–1,857x.

Measured 2026-07-20 — Rift built from master (924cf73) vs Mountebank 2.9.1, native processes (no Docker), oha at 50 keep-alive connections, 20s/scenario after warmup, each engine run alone on the same machine. Each figure is the median of 3 repetitions; per-scenario spread was ≤12% on the M4 (a laptop thermally throttles over a 30-minute run — both engines lost ~7% between the first and last repetition) and ≤5% on EPYC. Throughput scales with matching complexity: Rift stays flat while Mountebank's per-request cost grows with stub count and predicate type. Full methodology and all 13 scenarios: tests/benchmark. Your numbers will vary with hardware and config.

vs WireMock

WireMock is the most widely used JVM mock server. Same suite, same host, same load — Rift is 4.0x–14.0x its throughput, and the gap widens as matching work grows:

WorkloadWireMock → Riftp99
Simple static stub83,048 → 334,025 RPS (4.0x)7.0 ms → 2.4 ms
Deep path match (310 stubs)24,264 → 326,779 RPS (13.5x)31.6 ms → 2.5 ms
Regex path (100 patterns)48,982 → 311,815 RPS (6.4x)
JSON body equals63,814 → 314,939 RPS (4.9x)
Query match (last of 100)20,529 → 190,867 RPS (9.3x)

Rift stays roughly flat from a trivial stub to a 310-stub deep match (334k → 327k); WireMock falls from 83k to 24k. That shape matters more than the headline multiple.

This is an architecture comparison, not a quality judgement. WireMock is mature, well-engineered, and far more widely adopted than Rift, with a much larger ecosystem — extensions, a Kotlin DSL, OpenAPI-driven mocking, commercial support — and its in-process story on the JVM is genuinely good. If you are an all-JVM shop and WireMock is working for you, Rift's case is weaker. Rift vs WireMock covers both directions, including when not to switch.

Intel Xeon Platinum 8573C, 16 vCPU (GitHub ubuntu-16core), 2026-07-27. WireMock 3.9.1 on Temurin 21 vs Rift from master. oha at 256 keep-alive connections, 20s/scenario after a 10s warmup — identical for both engines, each run alone. Median of 3 reps; spread ≤5.6% (Rift ≤1.2%). WireMock's Jetty pool is pinned to 256 so its 10-thread default is not the ceiling — a fairness guarantee, not a speedup: the stock-default column lands within noise of it on this hardware. Its request journal is off, matching how Rift and Mountebank are measured. Ratios move with offered concurrency — the same suite at 50 connections measures 3.1x-8.1x — so always quote the connection count. Full methodology, both connection points, all 13 scenarios and two caveats we do not bury: docs/performance.

vs Microcks

Microcks is the Apache-2.0, CNCF-incubating alternative, and the one many teams reach for before a commercial tool. Same suite, same load — Rift is 13.6x–54.6x its throughput on the HTTP matching path:

WorkloadMicrocks → Riftp99
Simple static stub16,192 → 347,604 RPS (21.5x)78.9 ms → 2.3 ms
API first stub (1st of 310)6,457 → 338,592 RPS (52.4x)239.3 ms → 2.4 ms
Deep path match (310 stubs)6,420 → 338,404 RPS (52.7x)238.0 ms → 2.4 ms
No match6,487 → 354,170 RPS (54.6x)166.2 ms → 2.3 ms
Query match (last of 100)14,397 → 195,966 RPS (13.6x)74.8 ms → 4.0 ms

That multiple is larger than WireMock's, and it is the least interesting thing here. Microcks is not a slower mock server so much as a different kind of product: a spec-driven mocking and contract testing platform, with a web UI, multi-tenancy and eight protocols, built on Spring Boot with a datastore behind it. Raw stub-serving throughput is not what it was built for, and for most of its users it is not the binding constraint. Quoting 54x without that context would be misleading.

The genuinely interesting result is the shape. Microcks' first, middle and last API points land within 0.6% of each other, so — like Rift, and unlike WireMock and Mountebank — stub position costs it nothing; it resolves by path and verb rather than scanning candidates. So against Microcks Rift's advantage is absolute throughput and tail latency, not scan behaviour, and the "competitors pay per candidate stub" story does not apply to it.

Where Microcks is better, plainly: it generates mocks from OpenAPI/AsyncAPI/Postman specs so they cannot drift from your contract, it does contract testing against a live implementation, it speaks seven protocols Rift does not (AsyncAPI, Kafka, MQTT, AMQP, WebSocket, gRPC, GraphQL), and it has CNCF governance behind it. Rift has none of that. If your workflow starts from a spec or leaves HTTP, Microcks is the right tool and this table is beside the point. Rift vs Microcks covers both directions.

AMD EPYC 7763, 16 vCPU (GitHub ubuntu-16core), 2026-07-30. Microcks 1.14.0 on Temurin 21 as a native JVM (no container — a container's virtualised network is not a property of the engine) vs Rift from source. oha at 256 keep-alive connections, 20s/scenario after a 10s warmup, each engine run alone. Median of 3 reps; spread ≤3.3% (Rift ≤2.7%). Microcks' per-request invocation statistics and CORS policy are off, matching how WireMock's request journal is off and how Rift is measured — that change turned out to be worth nothing measurable (−4% to +2%), and the stock-defaults column is published anyway. Six of the 13 scenarios are comparable; the other seven are listed with the reason each is excluded rather than approximated. Full methodology, the stock-defaults column, and what the data does not support: docs/comparisons/microcks.

Full Feature Support

Everything Mountebank does:

  • Imposters - HTTP/HTTPS mock servers
  • Predicates - equals, contains, matches, exists, jsonpath, xpath, and, or, not
  • Responses - Static, proxy, injection
  • Behaviors - wait, decorate, copy, lookup
  • Proxy Mode - Record and replay

...and a good deal it doesn't:

Fault InjectionProbabilistic latency, error, and TCP faults — chaos testing without a sidecar
ScriptingRhai and JavaScript engines for dynamic responses, with a script check/script run CLI
Scenarios (FSM)Declarative state machines instead of hand-rolled stateful injection
Flow StatePer-flow key/value store, in-memory or Redis-backed
Correlated IsolationPer-flow stub and state partitioning, so parallel tests don't collide
Front DoorOne listener routing to many imposters by host, path, header or method
Single-Port GatewayReach every imposter through the admin port
Intercept ProxyTLS-MITM a hard-coded external HTTPS host — no mitmproxy needed
Stub AnalysisOverlap and conflict detection before a stub silently shadows another
Debug ModeX-Rift-Debug explains why a request matched, or didn't
Hot ReloadRe-read config without dropping the process
MetricsPrometheus endpoint
Lintingrift-lint validates configs in CI before they ever load
Terminal UIrift-tui for interactive imposter management
Embedding & FFIRun the engine in-process from Rust, or any language over the C ABI

Four official SDKs — Java, Scala, Node/TypeScript, Go — wrap all of it behind a typed DSL, and all four replay the same conformance corpus. See Language SDKs.


Quick Start

Run with Docker

# Pull and run
docker pull zainalpour/rift-proxy:latest
docker run -p 2525:2525 -p 4545:4545 zainalpour/rift-proxy:latest
# Create your first imposter
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d '{ "port": 4545, "protocol": "http", "stubs": [{ "predicates": [{ "equals": { "path": "/hello" } }], "responses": [{ "is": { "statusCode": 200, "body": "Hello, World!" } }] }] }'# Test it
curl http://localhost:4545/hello

Use Existing Mountebank Config

# Load your existing imposters.json
docker run -p 2525:2525 -v $(pwd)/imposters.json:/imposters.json \
zainalpour/rift-proxy:latest --configfile /imposters.json

Installation

Docker (Recommended)

docker pull zainalpour/rift-proxy:latest

Homebrew (macOS/Linux)

brew tap achird-labs/rift
brew install rift

Cargo (crates.io)

cargo install rift-http-proxy

Download Binary

Download pre-built binaries from GitHub Releases:

# Example for Linux x86_64 — set VERSION to the release you want
VERSION=v0.17.0
TARGET=x86_64-unknown-linux-gnu
curl -LO https://github.com/achird-labs/rift/releases/download/$VERSION/rift-$VERSION-$TARGET.tar.gz
tar -xzf rift-$VERSION-$TARGET.tar.gz
sudo mv rift-$VERSION-$TARGET/bin/* /usr/local/bin/
rift --version

Each archive unpacks to a bin/ directory containing rift (the server), rift-lint, rift-tui, and rift-verify.

Available platforms:

  • Linux: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, aarch64-unknown-linux-musl
  • macOS: x86_64-apple-darwin, aarch64-apple-darwin
  • Windows: x86_64-pc-windows-msvc

Build from Source

git clone https://github.com/achird-labs/rift.git
cd rift
cargo build --release
./target/release/rift-http-proxy

Node.js / npm

For Node.js projects, use the official rift-node SDK (@rift-vs/rift on npm). It runs the engine three ways — embedded in-process (FFI via the companion @rift-vs/rift-embedded package, no Docker), connected to any running admin endpoint, or as a managed spawned binary — with a fluent DSL for stubs/predicates/responses/scenarios, plus Vitest and Jest testkits. Requires Node.js >= 20, ESM-only, zero runtime dependencies:

npm install @rift-vs/rift
import{rift,imposter,onGet,okJson,times}from'@rift-vs/rift';awaitusingengine=awaitrift.embedded();// or rift.connect(url) / rift.spawn()constusers=awaitengine.create(imposter('users').stub(onGet('/api/users/1').willReturn(okJson({id: 1,name: 'Alice'}))));awaitfetch(`${users.url}/api/users/1`);awaitusers.verify(onGet('/api/users/1'),times(1));// throws with a diff on mismatch

Already on Mountebank, or migrating from the pre-monorepo @rift-vs/rift? The Mountebank-compatible create() stays available as a permanent drop-in, so adopting the typed DSL above is incremental, not a forced rewrite.

See the rift-node docs for the full feature surface.

Java / JVM

For JVM projects, use the official rift-java SDK. It runs the engine three ways — embedded in-process (Panama FFM, no Docker), connected to any running admin endpoint, or as a managed spawned binary — with a fluent DSL plus JUnit 5, Spring, and Testcontainers integrations. Available on Maven Central under io.github.achird-labs:

<dependency>
<groupId>io.github.achird-labs</groupId>
<artifactId>rift-java-core</artifactId>
<scope>test</scope>
</dependency>
try (Riftrift = Rift.embedded()) { // or Rift.connect(uri) / Rift.spawn()Imposterusers = rift.create(
imposter("users").stub(onGet("/api/users/1").willReturn(okJson("{\"id\":1}"))));
// point your SUT at users.uri(), then assert:users.verify(onGet("/api/users/1"), times(1));
}

See the rift-java docs for the full feature surface.

Scala

For Scala 3, use the official rift-scala SDK. It is effect-library-native — ZIO, Cats Effect 3 / FS2, or no effect system at all — over the same four transports (embedded, connect, spawn, container):

libraryDependencies +="io.github.achird-labs"%%"rift-scala-zio"%"0.1.4"%Test
importrift.dsl.*importrift.zio.Riftfor
users <-Rift.create(
imposter("users").record.stub(
get("/api/users/1").reply(ok.json("""{"id":1}"""))
)
)
_ <- callSut(users.uri) // point your SUT at users.uri
_ <- users.verify(get("/api/users/1"), 1)
yield ()
// .provideShared(Rift.embedded) — or Rift.connect(uri) / Rift.spawn() / Rift.container()

It also ships a zio-bddMockControl adapter, certified against zio-bdd's published conformance catalogue. See the rift-scala docs.

Go

For Go projects, use the official rift-go SDK. It runs the engine three ways — embedded in-process, connected to any running admin endpoint, or as a managed spawned binary — with a fluent DSL plus testing.T helpers.

The embedded transport loads the engine through purego rather than cgo, so CGO_ENABLED=0 keeps working: no C toolchain, no cross-compilation penalty, and nothing about a consumer's build changes by depending on it.

go get github.com/achird-labs/rift-go
go run github.com/achird-labs/rift-go/cmd/rift-fetch@latest -version v0.17.0
funcTestUserLookup(t*testing.T) {
users:=rifttest.Imposter(t, rift.NewImposter("users").
Stub(rift.OnGet("/api/users/1").
Return(rift.OKJSON(map[string]rift.JSON{"id": 1, "name": "Alice"}))))
callSUT(t, users.BaseURL()) // point your SUT at users.BaseURL()rifttest.AssertReceived(t, users, rift.OnGet("/api/users/1"), rift.Once())
}

Assertion counting runs through the engine's own predicate evaluator, so xpath, jsonpath and inject predicates mean the same thing in a verification as in a stub — and a failure reports the nearest non-matching request with the clauses it failed. See the rift-go docs.

All four SDKs

Java, Scala, Node/TypeScript and Go are all officially supported, and all four replay the same conformance corpus so their DSLs stay in lockstep with the engine grammar. The Language SDKs section collects the install snippets, hello-worlds, transport matrix and version-compatibility table.


Documentation

Getting Started

Mountebank Compatibility

Configuration

Features

Deployment

Reference


Example

{
"port": 4545,
"protocol": "http",
"name": "User Service",
"stubs": [
{
"predicates": [{ "equals": { "method": "GET", "path": "/users" } }],
"responses": [{
"is": {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": [{ "id": 1, "name": "Alice" }]
}
}]
},
{
"predicates": [{
"and": [
{ "equals": { "method": "GET" } },
{ "matches": { "path": "/users/\\d+" } }
]
}],
"responses": [{
"is": { "statusCode": 200, "body": { "id": 1, "name": "Alice" } }
}]
}
]
}

More examples in examples/.


Metrics

Prometheus metrics on :9090/metrics:

curl http://localhost:9090/metrics

Metrics include request counts, latency histograms, fault injection stats, and more.


CLI Tools

Rift includes additional command-line tools. All tools are included when you install via Homebrew or download release binaries.

rift-tui - Interactive Terminal UI

Manage imposters and stubs through an interactive terminal interface:

# If installed via Homebrew or release binary
rift-tui
# Connect to a different admin URL
rift-tui --admin-url http://localhost:2525

Features:

  • View and manage imposters with vim-style navigation (j/k)
  • Create, edit, and delete stubs with JSON editor
  • Generate curl commands for testing stubs
  • Import/export imposter configurations
  • Search and filter imposters and stubs
  • Real-time metrics dashboard

rift-verify - Stub Verification

Automatically test your imposters by generating requests from predicates:

rift-verify --show-curl

rift-lint - Configuration Linter

Validate imposter configuration files before loading:

# If installed via Homebrew or release binary
rift-lint ./imposters/
# Via Docker (for CI/CD)
docker run --rm -v $(pwd):/imposters zainalpour/rift-lint .# Via cargo
cargo install rift-lint
rift-lint ./imposters/

Development

# Build
cargo build --release
# Run tests
cargo test --all
# Run with debug logging
RUST_LOG=debug ./target/release/rift-http-proxy
# Run benchmarks (Rift vs Mountebank; see tests/benchmark/README.md)cd tests/benchmark && python3 scripts/bench_direct.py --run-all \
--rift-bin ../../target/release/rift-http-proxy \
--mb-bin ~/bench-mb/node_modules/mountebank/bin/mb

Used By

Rift powers HTTP mocking in the following projects:

  • zio-bdd — a Gherkin-style BDD testing framework for ZIO
  • zio-openfeature — a ZIO-native wrapper around the OpenFeature Java SDK

Using Rift somewhere? Open a PR to add it here.


Contributing

Contributions welcome! Please read our contributing guidelines and submit PRs.


License

Apache License 2.0 - see LICENSE for details.


Acknowledgments

  • Mountebank - The original service virtualization tool that inspired Rift's API and configuration format

Releases

Sponsor this project

Packages

Contributors

Languages