mirror

package
v0.18.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 10, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package mirror provides a request-shadowing (traffic-mirroring) middleware: it tees a copy of matched/sampled REQUESTS to a separate destination chain (a "canary"), fire-and-forget, without ever affecting the primary request or its response. Use it to exercise a new build with real production traffic.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Mirror

type Mirror struct {

	// Match selects which requests are eligible to mirror; nil matches all.
	Match func(r *http.Request) bool

	// Observe receives one MirrorInfo per decision/result; nil disables it. It is
	// called SYNCHRONOUSLY and concurrently — on the request goroutine for the
	// dispatch/drop outcomes (so it must be fast and non-blocking, or it stalls the
	// primary) and on worker goroutines for completed/panicked — so it must be safe
	// for concurrent use. See prom.Mirror for a ready, concurrency-safe hook.
	Observe func(info MirrorInfo)

	// ErrorLog logs a recovered mirror panic; nil uses the standard logger.
	ErrorLog *log.Logger

	// MarkHeader/MarkValue stamp the mirrored request so the canary can detect it and
	// no-op side effects. Default "X-Mirror"/"1". Set DisableMark to turn marking off.
	MarkHeader string
	MarkValue  string

	// SampleRate is the fraction in [0,1] of MATCHED requests to mirror. The zero
	// value means 1.0 (mirror all matched) — disable mirroring by NOT installing the
	// middleware, not by SampleRate=0. Values > 1 clamp to 1; negative clamps to 1.
	SampleRate float64

	// Timeout bounds a mirror's total in-system lifetime — queue wait plus the
	// round-trip — as one deadline set at dispatch. It is cooperative: the destination
	// chain must honor the request context (upstream/httputil do). Default 10s.
	Timeout time.Duration

	// MaxBodyBytes caps the request body buffered per mirror; a larger body skips the
	// mirror (the primary is unaffected). Default 1<<20.
	MaxBodyBytes int64

	// Workers is the fixed number of dispatch goroutines (the real concurrency cap).
	// Default 8.
	Workers int

	// QueueSize is the buffered job-queue depth; a full queue drops (never blocks the
	// primary). Default 256.
	QueueSize int

	// DisableBody, when true, always mirrors with no body (http.NoBody) — the
	// zero-buffer mode for large-payload services. The zero value mirrors bodies.
	DisableBody bool

	// DisableMark, when true, omits the MarkHeader stamp. The zero value marks (so a
	// canary can detect shadow traffic by default); set it for a fully transparent
	// mirror.
	DisableMark bool
	// contains filtered or unexported fields
}

Mirror is a traffic-shadowing Middleware. It tees the REQUEST (not the response) of matched/sampled requests to its destination chain on a fixed worker pool, fire-and-forget, and never affects the primary request. The primary always runs unmodified; a mirror that is slow, full, or panicking is dropped or recovered, not propagated.

Config fields and the destination chain (Use) are read once on the first ServeHandler and then FROZEN — set them before serving; a later Use is silently ignored (unlike block.Block, which rebuilds per request). Effective mirror concurrency is Workers, not Workers+QueueSize: the queue only absorbs bursts that drain at worker speed, so under a slow-but-responsive canary the pool caps at Workers and the surplus is dropped (DroppedFull) — size Workers for the canary's latency, and wire Observe to watch the drop counters.

The worker pool is started on first use and lives for the PROCESS LIFETIME (no Close): construct one Mirror per destination and reuse it. On graceful shutdown in-flight/queued mirrors are abandoned (each bounded only by Timeout), which is acceptable for fire-and-forget shadow traffic. End-to-end credentials (Authorization, Cookie) are replayed to the canary by design (replay fidelity); use Match to exclude sensitive routes from shadowing.

func New

func New() *Mirror

New creates a request-mirroring middleware. Configure the mirror destination with Use (typically upstream.SingleHost, or a balancer wrapped by upstream.New) and any config fields, then install it ahead of the real chain.

Example

Shadow a sample of production GET traffic to a canary backend, fire-and-forget: the mirror never affects the primary request or its response. Mark and sample so the canary can detect (and no-op side effects of) shadow traffic and so only a fraction is teed; Observe wires outcome/latency metrics.

package main

import (
	"net/http"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/mirror"
	"github.com/moonrhythm/parapet/pkg/prom"
	"github.com/moonrhythm/parapet/pkg/upstream"
)

func main() {
	mr := mirror.New()
	mr.Match = func(r *http.Request) bool { return r.Method == http.MethodGet }
	mr.SampleRate = 0.1        // shadow 10% of matched requests
	mr.Observe = prom.Mirror() // optional outcome/latency metrics
	mr.Use(upstream.SingleHost("canary:8080", &upstream.HTTPTransport{}))

	s := parapet.NewFrontend()
	s.Use(mr)                                                          // tees, then falls through to the real chain
	s.Use(upstream.SingleHost("prod:8080", &upstream.HTTPTransport{})) // the production backend
}

func (*Mirror) ServeHandler

func (m *Mirror) ServeHandler(next http.Handler) http.Handler

ServeHandler implements parapet.Middleware.

func (*Mirror) Stats

func (m *Mirror) Stats() (dispatched, dropFull, dropOversize, completed, panicked uint64)

Stats returns the lock-free dispatch counters for tests/introspection.

func (*Mirror) Use

func (m *Mirror) Use(x parapet.Middleware)

Use appends a middleware to the mirror destination chain. SETUP ONLY: it must be called before the first request; the chain is frozen on first ServeHandler and a later Use is silently ignored.

func (*Mirror) UseFunc

func (m *Mirror) UseFunc(x parapet.MiddlewareFunc)

UseFunc appends a middleware func to the mirror destination chain. SETUP ONLY (see Use).

type MirrorFunc

type MirrorFunc func(info MirrorInfo)

MirrorFunc is the observation-hook shape (mirrors upstream.RoundTripFunc), returned by prom.Mirror for wiring into Mirror.Observe.

type MirrorInfo

type MirrorInfo struct {
	Outcome  Outcome
	Status   int           // set on OutcomeCompleted
	Duration time.Duration // set on OutcomeCompleted
}

MirrorInfo reports one mirror decision/result to Observe.

type Outcome

type Outcome uint8

Outcome classifies one mirror decision/result, reported via Observe.

const (
	// OutcomeDispatched: the mirror request was enqueued for a worker.
	OutcomeDispatched Outcome = iota
	// OutcomeCompleted: a worker finished the round-trip (Status and Duration set).
	OutcomeCompleted
	// OutcomeDroppedFull: the queue was full; the mirror was dropped.
	OutcomeDroppedFull
	// OutcomeDroppedOversize: the body exceeded MaxBodyBytes; the mirror was skipped.
	OutcomeDroppedOversize
	// OutcomePanicked: the mirror chain panicked (recovered; the proxy is unaffected).
	OutcomePanicked
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL