flapwire
v0.2.3
Published
Local HTTP/HTTPS proxy that degrades traffic realistically for resilience testing.
Maintainers
Readme
Flapwire
Local HTTP/HTTPS proxy that degrades traffic on purpose — jittered latency, random connection drops, periodic blackouts — so you can see what breaks before production does.
Forward proxy (classic)
npx flapwire --profile slow-3g
# then, in another terminal:
curl -x http://127.0.0.1:8080 http://example.com/Reverse proxy
Point your app at Flapwire instead of the upstream and it'll degrade on the way through — no browser proxy config, no /etc/hosts, no flags. Plain HTTP requests and WebSocket upgrades (e.g. Next.js / Vite HMR) are both forwarded, and the same levers apply to the WebSocket handshake. Upstreams on https:// work too.
Single target:
npx flapwire --target http://localhost:3000 --profile flaky-wifi
# listens on http://127.0.0.1:13000 → http://localhost:3000Multiple services in one process:
npx flapwire \
--route 13000=http://localhost:3000 \
--route 15173=http://localhost:5173 \
--route 18080=http://localhost:8080 \
--profile train-wifiListen ports are the upstream port with a leading 1 by convention (3000 → 13000). When that doesn't fit (out of range, already taken, duplicated), Flapwire picks a free port and tells you which one.
The proxy logs each request:
GET http://example.com/ → 200 (412ms)
GET http://example.com/favicon.ico → drop
GET /api/me → 504 blackout (380ms)Profiles
| Name | Latency (base ± σ) | Drop rate | Blackout |
| ------------ | ------------------ | --------- | ---------------- |
| fast-3g | 100ms ± 50ms | 0% | — |
| slow-3g | 400ms ± 200ms | 0.5% | — |
| flaky-wifi | 150ms ± 300ms | 2% | — |
| train-wifi | 500ms ± 400ms | 2% | 4s every 60s |
How the levers work
Each profile is a mix of three independent levers.
Latency — every request is delayed by a sample drawn from a normal distribution centred on baseMs with standard deviation jitterMs, clamped to zero. Roughly 68% of samples fall within baseMs ± jitterMs, 95% within ±2 × jitterMs. The delay is applied before the response is written, so the client experiences it as time-to-first-byte — exactly what happens on a real slow link.
Drop — before latency kicks in, each incoming connection is independently discarded with probability connectionDropRate (a value between 0 and 1). "Discarded" here means the TCP socket is destroyed with no response at all, the way a client would see a packet-loss or RST event from the network.
Blackout — windows where the proxy stops forwarding entirely. everySeconds is the cycle length, durationSeconds is how much of each cycle the blackout covers. The blackout lands at the end of every cycle: for the last durationSeconds of each window, existing connections are torn down and new requests are answered with 504 Gateway Timeout — after the profile's latency, so the stall feels like a real upstream going unreachable rather than an instant reset. WebSocket upgrade attempts during a blackout are refused the same way: the TCP socket is closed.
A lever with no configured value is simply inactive: no latency, no drops, or no blackout cycle at all.
Options
flapwire [--profile <name>] [--port <number>] [--target <url>] [--route <PORT=URL> ...] [--config <path>]--profile,-p— one offast-3g,slow-3g,flaky-wifi,train-wifi. Default:slow-3g.--port— port to listen on. Forward mode default is8080. In--targetmode use an explicit number, orauto(or leave it off) to let Flapwire derive it from the upstream port.--target <url>— single reverse-proxy upstream. Mutually exclusive with--route.--route <PORT=URL>— repeatable; open one listen port per route, all sharing the same profile.--config <path>,-c— path to a config file (default:./flapwire.config.yamlif present).
If neither --target nor --route is given, Flapwire runs as a forward proxy (v0.1 behaviour).
Config file
If flapwire.config.yaml exists in the current directory, Flapwire reads it on startup. CLI flags still work and override the file field by field — same convention as Vite, Next, Playwright.
profile: flaky-wifi
routes:
- listen: 13000
target: http://localhost:3000
- listen: 15173
target: https://localhost:5173
upstreamCa: ./certs/dev-ca.pem # optional; trusts a self-signed upstreamSingle-target shape:
profile: slow-3g
target: http://localhost:3000
port: 13000Forward mode is the default — omit both target and routes.
Admin API
Set admin.port in the config (or --admin-port) to expose a small control plane on 127.0.0.1. No auth — it's a localhost-only dev tool. Useful for flipping state during a test run without restarting the proxy.
admin:
port: 17070# switch profile live
curl -X POST http://127.0.0.1:17070/admin/profile -d '{"name":"flaky-wifi"}'
# force a 5-second blackout right now
curl -X POST http://127.0.0.1:17070/admin/blackout -d '{"durationSeconds":5}'
# make the next 3 requests fail with 503
curl -X POST http://127.0.0.1:17070/admin/fail -d '{"status":503,"count":3}'
# read current state
curl http://127.0.0.1:17070/admin/statusIn multi-route reverse mode, every change is fanned out to all routes at once.
Failure injection
Make Flapwire short-circuit specific requests with an HTTP error or a timeout — useful for verifying that retry, fallback, and timeout-handling paths actually work.
failures:
- path: "^/api/checkout"
method: POST
sample: 0.1 # 10% of matching requests
status: 503
- path: "^/api/slow"
timeout: true # hold the socket open; client times itself outRules match in order — first hit wins. The path field is treated as a case-insensitive regex; method and sample are optional. Exactly one of status or timeout must be set.
You can also push rules at runtime through the admin API:
curl -X POST http://127.0.0.1:17070/admin/failures \
-d '{"rules":[{"path":"^/api/payment","status":504}]}'
curl http://127.0.0.1:17070/admin/failuresPOST /admin/failures replaces the live rule list; GET /admin/failures reads it back.
HTTPS
Flapwire can terminate TLS for both forward-proxied browsers (CONNECT) and reverse-proxy upstreams on https://. TLS termination needs a local CA that the OS trusts; set it up once with:
flapwire trust
# sudo will prompt — the CA is written to ~/.config/flapwire/ca.pem and added to the system trust storeAfter that, run Flapwire normally. Forward mode: point your browser's HTTP(S) proxy at 127.0.0.1:8080 and visit an HTTPS site. Reverse mode: point --target or --route at an https:// upstream.
The CA is issued for "Flapwire Local CA" (10-year validity) and signs one leaf cert per hostname on demand. flapwire trust --uninstall removes it. The CA's private key never leaves your machine.
Supported trust stores: macOS system keychain (via security), Linux with update-ca-certificates (Debian/Ubuntu) or update-ca-trust (Fedora/RHEL). On Windows, flapwire trust prints the certutil command to run in an elevated shell.
Why not ...
- Chrome DevTools throttling — lives in the browser, can't be scripted, applies a flat delay. Fine for one manual check; not usable in CI or for non-browser clients.
- Playwright / Cypress network emulation — same browser-only limitation, no real jitter, loss, or blackout.
tc/netem— kernel-level, far more powerful, Linux-only without contortions, steep learning curve. If you need packet-level fidelity, reach fortc.- toxiproxy — closest neighbour. More features, more configurability, a running daemon and its own config format. Flapwire is deliberately smaller.
Not in this release
No bandwidth throttling, external community profiles, or CI helpers yet. A web UI and a CI integration are on the way in later milestones.
License
MIT. See LICENSE.
This is a side project maintained in spare time. Issues are read but response time is not guaranteed.
