switchboard

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 0 Imported by: 0

README

Switchboard

Prototype for programmable reverse proxy rules.

Users write request rules in Go, compile them to WebAssembly with TinyGo, upload immutable bundles to object storage, and proxy instances poll a channel pointer to hot-swap the active rule without restarting the proxy.

Switchboard keeps the long-lived dataplane stable, moves fast-changing request policy into versioned Wasm guests, and activates versions only after validation. It applies that pattern to ordinary reverse proxy deployments instead of requiring a custom CDN or edge network.

Rule deployments do not restart the proxy. Bundles are downloaded, verified, compiled, warmed, and validated off the request path, then activated with an atomic swap. In-flight requests continue using the runtime version they started with. If activation fails, the last known-good version remains active.

func Handle(req sdk.Request) sdk.Action {
	if req.Path() == "/blocked" {
		return sdk.Deny(403).WithReason("blocked-path")
	}

	return sdk.Next().SetRequestHeader("x-powered-by", "switchboard")
}

Architecture

Switchboard architecture

  • CLI: init, build, test, eval, replay, deploy, promote, rollback, and more
  • Registry: S3-compatible object storage, local file://, or read-only https://
  • Proxy: Caddy handler module (http.handlers.switchboard), standalone switchboard serve, or embeddable Go middleware
  • Runtime: wazero with enforced timeouts, memory caps, and output validation
  • Guests: TinyGo WASI modules against a small host ABI (switchboard/v4)

Install

go install github.com/ethndotsh/switchboard/cmd/switchboard@latest

Caddy with the module built in:

docker pull ghcr.io/ethndotsh/switchboard-caddy:latest
# or
xcaddy build --with github.com/ethndotsh/switchboard/caddy@latest

You'll also need TinyGo to build rules.

Quickstart

No object store required; the file:// registry works out of the box:

mkdir my-rules && cd my-rules
switchboard init --name my-rules --registry file://./registry

switchboard build      # compile the rule + embed its tests
switchboard test       # run the behavioral suite
switchboard deploy     # upload + repoint the channel

switchboard serve --listen :8080 --upstream localhost:3000 \
  --registry file://./registry --channel prod

Edit the rule, switchboard build && switchboard deploy, and the running proxy hot-swaps it within seconds. Broke something? switchboard rollback --channel prod.

Documentation

Full docs live in docs/: concepts, guides, and a complete CLI/SDK/Caddyfile reference.

cd docs && pnpm install && pnpm dev

Highlights:

  • Quickstart: first rule to live traffic in five minutes
  • Concepts: architecture, bundles/channels/revisions, the reliability model
  • Guides: writing rules, testing, replaying production traffic, deploying, Caddy integration, registries
  • Reference: every CLI flag, Caddyfile directive, SDK method, and schema

Examples

Real-world rules, each with a behavioral test suite, under examples/: auth gating, legacy redirects, security headers, maintenance mode, metadata-driven canary routing, a data-file-driven IP allowlist, and a typed-config feature-flags rule.

Limitations

Rules cannot read request/response bodies, make network calls, or hold state. They can read immutable data files baked into the bundle (allowlists, redirect maps, cohort tables), which keeps lookups deterministic and replayable without reintroducing state. Switchboard is not a CDN, cache, or hosted control plane. Caddy is the reference adapter.

Prior art

Architectural lessons from Railway's Hikari CDN (stable dataplane, versioned guests, reconcile + atomic swap) and Arcjet's production wazero writeups (precompile, never instantiate on the request path).

License

See LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action

type Action struct {
	Decision Decision          `json:"decision"`
	Patch    RequestPatch      `json:"patch,omitempty"`
	Response Response          `json:"response,omitempty"`
	Metadata map[string]string `json:"metadata,omitempty"`
	Reason   string            `json:"reason,omitempty"`
}

type Decision added in v0.2.0

type Decision string
const (
	DecisionNext     Decision = "next"
	DecisionDeny     Decision = "deny"
	DecisionRedirect Decision = "redirect"
	DecisionRewrite  Decision = "rewrite"
	DecisionRespond  Decision = "respond"
)

type HeaderOp added in v0.0.3

type HeaderOp struct {
	Op    HeaderOpType `json:"op"`
	Name  string       `json:"name"`
	Value string       `json:"value,omitempty"`
}

type HeaderOpType added in v0.0.3

type HeaderOpType string
const (
	HeaderOpSet    HeaderOpType = "set"
	HeaderOpAdd    HeaderOpType = "add"
	HeaderOpDelete HeaderOpType = "delete"
)

type Request

type Request struct {
	Method     string              `json:"method"`
	Scheme     string              `json:"scheme,omitempty"`
	Host       string              `json:"host,omitempty"`
	Path       string              `json:"path"`
	RawQuery   string              `json:"raw_query,omitempty"`
	Protocol   string              `json:"protocol,omitempty"`
	Headers    map[string][]string `json:"headers"`
	RemoteAddr string              `json:"remote_addr,omitempty"`
	ClientIP   string              `json:"client_ip,omitempty"`
	TLS        bool                `json:"tls,omitempty"`
}

type RequestPatch added in v0.2.0

type RequestPatch struct {
	Headers []HeaderOp `json:"headers,omitempty"`
	Host    *string    `json:"host,omitempty"`
	Path    *string    `json:"path,omitempty"`
	Query   *string    `json:"query,omitempty"`
}

RequestPatch mutates the request before it continues down the chain; nil pointer fields leave the component unchanged.

func (RequestPatch) IsZero added in v0.2.0

func (p RequestPatch) IsZero() bool

type Response added in v0.2.0

type Response struct {
	Status   int        `json:"status,omitempty"`
	Location string     `json:"location,omitempty"`
	Headers  []HeaderOp `json:"headers,omitempty"`
	Body     []byte     `json:"body,omitempty"`
}

Directories

Path Synopsis
abi
adapters
cmd
switchboard command
e2e
rules/v1 command
rules/v2 command
examples
ab-canary-routing
Package abcanaryrouting deterministically routes a fixed slice of users to a canary backend.
Package abcanaryrouting deterministically routes a fixed slice of users to a canary backend.
admin-gate
Package admingate protects /admin/* behind a role cookie.
Package admingate protects /admin/* behind a role cookie.
basic command
chained command
feature-flags
Package featureflags drives request policy from a typed config file bundled with the rule.
Package featureflags drives request policy from a typed config file bundled with the rule.
ip-allowlist
Package ipallowlist denies any request whose client IP is not in an allowlist bundled with the rule.
Package ipallowlist denies any request whose client IP is not in an allowlist bundled with the rule.
legacy-redirects
Package legacyredirects permanently redirects retired URLs to their modern replacements so old links, bookmarks, and search results keep working after a site restructure.
Package legacyredirects permanently redirects retired URLs to their modern replacements so old links, bookmarks, and search results keep working after a site restructure.
maintenance-mode
Package maintenancemode short-circuits requests with a static 503 page while the origin is being worked on.
Package maintenancemode short-circuits requests with a static 503 page while the origin is being worked on.
security-headers
Package securityheaders stamps baseline security headers onto every response: clickjacking protection, MIME-sniffing protection, a strict referrer policy, and HSTS on TLS connections.
Package securityheaders stamps baseline security headers onto every response: clickjacking protection, MIME-sniffing protection, a strict referrer policy, and HSTS on TLS connections.
internal
bundlecache
Package bundlecache persists the last activated bundle per channel so a proxy can bootstrap after restart without reaching the registry.
Package bundlecache persists the last activated bundle per channel so a proxy can bootstrap after restart without reaching the registry.
replay
Package replay runs captured Caddy access logs through two rule bundles and reports how their decisions differ.
Package replay runs captured Caddy access logs through two rule bundles and reports how their decisions differ.
ruletest
Package ruletest parses and runs declarative behavioral test suites against a compiled rule; the CLI and the engine's activation gate share it.
Package ruletest parses and runs declarative behavioral test suites against a compiled rule; the CLI and the engine's activation gate share it.

Jump to

Keyboard shortcuts

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