Sign inSign up

tibtof/httptape

By tibtof

•Updated 4 months ago

HTTP traffic recording, redaction, and replay — embeddable Go library, CLI, and 6 MB Docker image.

Image
Security
Integration & delivery
Developer tools
0

10K+

tibtof/httptape repository overview

httptape logo

⁠Record, Redact, Replay

HTTP traffic recording, redaction, and replay.
Embeddable Go library · CLI · Docker · Testcontainers

Go Reference Tests OpenSSF Scorecard License Docker Image Size


httptape captures HTTP request/response pairs (including SSE streams), redacts sensitive data on write, and replays them as a mock server. Think WireMock, but with a 3 MB Docker image, an embeddable Go library, SSE record/replay with per-event timing, and a redaction pipeline built into the core.

Docs: vibewarden.dev/docs/httptape⁠ · From: VibeWarden⁠

The 3 Rs:

  1. Record -- capture real HTTP traffic via a transparent http.RoundTripper
  2. Redact -- strip secrets and PII on write, before anything touches disk
  3. Replay -- serve recorded fixtures as a deterministic mock server

⁠Why httptape?

  • WireMock requires Java -- separate process, 200 MB+ memory, can't embed in a Go binary
  • Go mocking libraries (gock, httpmock) only work inside test code -- no standalone server, no recording, no fixture management
  • json-server / Mockoon -- no recording, no redaction, manual fixture writing only
  • Nobody does redaction -- existing tools record raw traffic including secrets and PII. httptape redacts on write -- sensitive data never hits disk
  • SSE record and replay -- record Server-Sent Event streams (LLM completions, real-time feeds) with per-event timing, replay them with configurable speed (realtime, accelerated, or instant), and redact PII from individual event payloads. No other Go mocking tool does this

⁠Use cases

⁠Integration testing

Record real API interactions once, replay forever. Deterministic CI without live API credentials.

store := httptape.NewMemoryStore()
rec := httptape.NewRecorder(store, httptape.WithSanitizer(sanitizer))
defer rec.Close()

client := &http.Client{Transport: rec}
// ... hit real APIs, fixtures are recorded and redacted ...

srv := httptape.NewServer(store)
ts := httptest.NewServer(srv)
// ... replay against ts.URL in your tests ...
⁠Frontend-first development

Use httptape as a mock backend while building your UI -- no real backend needed.

# Hand-write fixtures or record from a staging API
httptape record --upstream https://staging-api.example.com \
    --fixtures ./mocks --config redact.json

# Serve as a mock backend for your frontend
httptape serve --fixtures ./mocks --port 3001

Your frontend on localhost:3000 hits httptape on localhost:3001. Edit JSON fixture files, and the next request picks up the changes -- instant hot-reload.

⁠Production traffic capture

Record a sample of live traffic, safely redacted:

docker run -v ./fixtures:/fixtures -v ./config.json:/config/config.json \
    tibtof/httptape record \
    --upstream https://api.internal:8080 \
    --fixtures /fixtures --config /config/config.json

Sensitive data (secrets, PII) is redacted before it touches disk. Export redacted fixtures for dev/CI use.

⁠Fallback proxy

Use proxy mode for frontend development with automatic fallback to cached responses when the backend is unavailable:

httptape proxy --upstream https://api.example.com \
    --fixtures ./cache --config redact.json

When the upstream is reachable, requests are forwarded and responses are cached in two tiers (L1 in-memory, L2 on disk). When the upstream is down, httptape transparently serves cached responses. See Proxy Mode⁠ for details.

⁠Recording LLM streaming responses

Record SSE streams from OpenAI, Anthropic, or any SSE-based API. Each event is stored individually with timing metadata, so replay can simulate the original streaming behavior.

store := httptape.NewMemoryStore()

// Record -- SSE detection is automatic for text/event-stream responses.
sanitizer := httptape.NewPipeline(
    httptape.RedactHeaders("Authorization"),
    httptape.RedactSSEEventData("$.choices[*].delta.content"),
)
rec := httptape.NewRecorder(store,
    httptape.WithSanitizer(sanitizer),
    httptape.WithRoute("openai"),
)
defer rec.Close()

client := &http.Client{Transport: rec}
// Hit the real LLM API -- SSE events are captured with per-event timing.
resp, _ := client.Post("https://api.openai.com/v1/chat/completions",
    "application/json", strings.NewReader(`{
        "model": "gpt-4", "stream": true,
        "messages": [{"role": "user", "content": "Hello"}]
    }`))
io.Copy(io.Discard, resp.Body)
resp.Body.Close()

// Replay with instant timing for fast tests.
srv := httptape.NewServer(store, httptape.WithSSETiming(httptape.SSETimingInstant()))
ts := httptest.NewServer(srv)
defer ts.Close()
// Point your code at ts.URL -- streaming responses replay instantly.

See Recording⁠ and Replay⁠ for details on SSE support.

⁠Install

Go library:

go get github.com/VibeWarden/httptape

CLI:

go install github.com/VibeWarden/httptape/cmd/httptape@latest

Docker (~3 MB, multi-arch):

docker pull tibtof/httptape

⁠Quick start

⁠Record
store := httptape.NewMemoryStore()
rec := httptape.NewRecorder(store, httptape.WithRoute("github-api"))
defer rec.Close()

client := &http.Client{Transport: rec}
resp, err := client.Get("https://api.github.com/users/octocat")
// Tape is automatically saved to store
⁠Redact

Strip secrets and fake PII -- on write, before anything hits disk:

sanitizer := httptape.NewPipeline(
    httptape.RedactHeaders("Authorization", "Cookie"),
    httptape.RedactBodyPaths("$.card.number", "$.ssn"),
    httptape.FakeFields("my-seed", "$.email", "$.user_id"),
)
rec := httptape.NewRecorder(store, httptape.WithSanitizer(sanitizer))

Or declaratively via JSON config:

{
  "version": "1",
  "rules": [
    {"action": "redact_headers", "headers": ["Authorization", "Cookie"]},
    {"action": "redact_body", "paths": ["$.card.number", "$.ssn"]},
    {"action": "fake", "seed": "my-seed", "paths": ["$.email", "$.user_id"]}
  ]
}
⁠Replay
srv := httptape.NewServer(store)
ts := httptest.NewServer(srv)
defer ts.Close()

resp, err := http.Get(ts.URL + "/users/octocat")
⁠Match

Composable matching with weighted scoring:

srv := httptape.NewServer(store,
    httptape.WithMatcher(httptape.NewCompositeMatcher(
        httptape.MethodCriterion{},                                        // score: 1
        httptape.PathCriterion{},                                          // score: 2
        httptape.HeadersCriterion{Key: "Accept", Value: "application/json"}, // score: 3
        httptape.QueryParamsCriterion{},                                   // score: 4
        httptape.BodyHashCriterion{},                                      // score: 8
    )),
)
⁠Store
// In-memory (for tests)
mem := httptape.NewMemoryStore()

// Filesystem (for fixtures)
fs := httptape.NewFileStore(httptape.WithDirectory("./testdata/fixtures"))
⁠Proxy (fallback-to-cache)
l1 := httptape.NewMemoryStore()
l2, _ := httptape.NewFileStore(httptape.WithDirectory("./cache"))

proxy := httptape.NewProxy(l1, l2,
    httptape.WithProxySanitizer(sanitizer),
)
client := &http.Client{Transport: proxy}
// Upstream reachable: real response returned, cached in L1 + L2
// Upstream down: cached response returned transparently
⁠Proxy health endpoints (opt-in)

The proxy can expose a small technical surface for operators and downstream UIs to react to upstream state changes in real time. Off by default; opt in with --health-endpoint (CLI) or WithProxyHealthEndpoint() (library):

httptape proxy --upstream https://api.example.com --fixtures ./cache \
    --health-endpoint --upstream-probe-interval 2s
# JSON snapshot
curl http://localhost:8081/__httptape/health
# {"state":"live","upstream_url":"https://api.example.com","probe_interval_ms":2000,"since":"2026-04-16T10:00:00Z","last_probed_at":"2026-04-16T10:00:02Z"}

# SSE stream — one event on connect, one per state transition
curl -N http://localhost:8081/__httptape/health/stream
# retry: 2000
#
# data: {"state":"live", ... }
#
# data: {"state":"l1-cache", ... }

state mirrors the existing X-Httptape-Source header (live, l1-cache, l2-cache). With both flags absent, no endpoints are mounted and no probe goroutine is started — behavior is byte-for-byte unchanged.

⁠SSE replay

Replay recorded SSE streams with configurable timing. Use SSETimingInstant() for fast tests:

srv := httptape.NewServer(store,
    httptape.WithSSETiming(httptape.SSETimingInstant()),
)
ts := httptest.NewServer(srv)
defer ts.Close()

resp, _ := http.Get(ts.URL + "/v1/chat/completions")
// Events are streamed back instantly -- no waiting for original timing.

Other timing modes: SSETimingRealtime() preserves original inter-event gaps, SSETimingAccelerated(10) replays 10x faster.

⁠Import / Export
// Export redacted fixtures as a portable bundle
r, _ := httptape.ExportBundle(ctx, store,
    httptape.WithRoutes("stripe-api"),
    httptape.WithSince(time.Now().Add(-24*time.Hour)),
)

// Import on another machine
httptape.ImportBundle(ctx, store, r)

⁠CLI

httptape serve   --fixtures ./mocks --port 8081
httptape record  --upstream https://api.example.com --fixtures ./mocks --config redact.json
httptape proxy   --upstream https://api.example.com --fixtures ./cache --config redact.json
httptape export  --fixtures ./mocks --output bundle.tar.gz
httptape import  --fixtures ./mocks --input bundle.tar.gz

⁠Docker

Pull from either registry — the same image is published to both on every release:

docker pull tibtof/httptape           # Docker Hub
docker pull ghcr.io/vibewarden/httptape   # GHCR

Examples below use tibtof/httptape; substitute ghcr.io/vibewarden/httptape freely.

# Replay mode
docker run -v ./mocks:/fixtures -p 8081:8081 tibtof/httptape serve --fixtures /fixtures

# Record mode (with redaction)
docker run -v ./mocks:/fixtures -v ./config.json:/config/config.json -p 8081:8081 \
    tibtof/httptape record --upstream https://api.example.com \
    --fixtures /fixtures --config /config/config.json

# Proxy mode (with fallback-to-cache)
docker run -v ./cache:/fixtures -v ./config.json:/config/config.json -p 8081:8081 \
    tibtof/httptape proxy --upstream https://api.example.com \
    --fixtures /fixtures --config /config/config.json

⁠Testcontainers

import httptapetest "github.com/VibeWarden/httptape/testcontainers"

container, err := httptapetest.RunContainer(ctx,
    httptapetest.WithFixturesDir("./testdata/fixtures"),
)
defer container.Terminate(ctx)

// container.BaseURL() returns the mock server URL
resp, _ := http.Get(container.BaseURL() + "/api/users")

⁠Examples

Runnable end-to-end examples live in examples/⁠:

  • ts-frontend-first⁠ — Vite + React talking to an httptape proxy with live source-state updates over SSE. Demonstrates fallback-to-cache (live → L1 → L2), per-event redaction, and the /__httptape/health endpoint.

More examples coming. See examples/README.md⁠ for the index.

⁠How it compares

FeaturehttptapeWireMockjson-serverMSWgock
Embeddable in Goyesno (Java)no (Node)no (browser)yes
Standalone serveryesyesyesnono
Docker3 MB200 MB+50 MB+n/an/a
Recordingyesyesnonono
Redaction on writeyesnononono
Deterministic fakingyesnononono
Proxy with fallbackyesnononono
SSE record/replayyesnonopartialno
Frontend mock backendyesyesyesyes (browser)no
Fixture import/exportyespartialnonono
DependencieszeroJVMnpmnpm1

⁠Key design decisions

DecisionChoiceReason
Dependenciesstdlib onlyZero transitive deps for embedders
RedactionOn writeSensitive data never touches disk
FakingHMAC-SHA256Deterministic -- same input always produces the same fake
FixturesJSONHuman-readable, easy to inspect and edit
StoragePluggableMemoryStore for tests, FileStore for persistence
RecordingAsync by defaultNon-blocking, minimal hot-path overhead
MatchingComposableStart simple, add specificity as needed
ProxyL1/L2 cachingRaw in-memory for session, redacted on disk for persistence

⁠Documentation

Full docs at vibewarden.dev/docs/httptape⁠.

⁠About

httptape is developed as part of VibeWarden⁠. For the full docs, guides, and other projects from the VibeWarden team, visit vibewarden.dev⁠.

⁠License

Apache 2.0⁠

Tag summary

Content type

Image

Digest

sha256:eff550ccc…

Size

3.4 MB

Last updated

4 months ago

docker pull tibtof/httptape