weir

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

README

Weir

Weir is a Go library for shared HTTP caching in front of an origin that has to stay up. The request it forwards is the request it keyed, so a header the cache ignores cannot poison an entry, and a stampede cannot reach the origin faster than a budget you set.

A weir is a low dam that regulates flow without stopping it. Adapters for net/http and Caddy handle the wire; Weir makes the decision for each request.

ci Go reference release license

Documentation: weir.ashwinsathian.com

See it work

package main

import (
	"log"
	"net/http"
	"net/url"
	"time"

	"github.com/AshwinSathian/weir"
	"github.com/AshwinSathian/weir/weirhttp"
)

func main() {
	target, _ := url.Parse("http://localhost:9000") // scheme and host; a path is ignored
	e, err := weir.New(weir.Config{})               // the zero Config is valid
	if err != nil {
		log.Fatal(err)
	}
	srv := &http.Server{
		Addr:              ":8080",
		Handler:           weirhttp.Handler(e, &weirhttp.TransportOrigin{Target: target}),
		ReadHeaderTimeout: 10 * time.Second,
	}
	log.Fatal(srv.ListenAndServe()) // on shutdown: srv.Shutdown(ctx), then e.Close(ctx)
}

examples/weirproxy is this program with flags. Run it in front of an origin that answers Cache-Control: max-age=60 and ask twice:

$ go run github.com/AshwinSathian/weir/examples/weirproxy@v1.0.1 -listen :8080 -origin http://localhost:9000
$ curl -si localhost:8080/ | grep -i -e '^age' -e cache-status
Cache-Status: Weir; fwd=uri-miss; fwd-status=200; stored
$ curl -si localhost:8080/ | grep -i -e '^age' -e cache-status
Age: 13
Cache-Status: Weir; hit; ttl=43

The first request went to the origin and was stored. The second, 13 seconds later, never left the process. Its ttl is 43 and not 47 because jitter took four seconds off this entry's lifetime, so entries stored together do not expire together. On Caddy the same thing is a weir { name site-a } block in front of reverse_proxy; see the quickstart.

What you get

  • Coalescing. Concurrent requests for one key share one origin fetch, with a bound on how long followers wait and on how long a slow leader keeps the flight (FR-COA-1). Coalescing
  • Jitter and early refresh. Each lifetime is shortened by up to 10%, and a hot entry is refreshed in the background shortly before it expires (FR-FRS-5, FR-FRS-6). Freshness and staleness
  • An origin budget. Every fetch holds a slot from a limiter (64 by default) with a per-path share, so 10,000 keys expiring at once reach the origin 64 at a time (FR-LIM-1). Origin protection
  • Stale serving and a breaker. Stale-while-revalidate, stale-if-error and a circuit breaker turn an origin outage into stale content or a fast 503 (FR-STL-1, FR-CB-1). Same page.
  • Purges that do not stampede. A soft purge marks entries stale and lets them revalidate through the limiter; a hard purge removes them. By URL, by RFC 9875 group, or everything (FR-PRG-1). Purging
  • A cache key that is a security boundary. On a cacheable request the origin receives only keyed, allowed and protocol headers (FR-FWD-1, INV-1). The cache key

What Weir is not

A CDN, a WAF, a per-client rate limiter, a private (browser) cache, or a cache for application data. It does not store 206 responses (it answers single ranges from complete objects), has no prefix purge, and the memory store does not share a cache between nodes: several nodes need the Valkey store. It bounds origin load, so put a rate limiter in front of it for floods of distinct paths.

Install

go get github.com/AshwinSathian/weir@v1.0.1                    # the engine, weirhttp, the memory store
go get github.com/AshwinSathian/weir/observe/prom@v1.0.1       # Prometheus metrics
go get github.com/AshwinSathian/weir/store/valkey@v1.0.1       # shared store for several nodes
xcaddy build --with github.com/AshwinSathian/weir@v1.0.1 --with github.com/AshwinSathian/weir/caddy@v1.0.1   # Caddy with the weir handler

The four modules are tagged together and share a version number, so use the same one for all of them. The Caddy module has the import path github.com/AshwinSathian/weir/caddy and the Go package name weircaddy; it is tested only against the root module at its own version, which is why xcaddy gets both pinned.

The minimum is Go 1.27.0, and CI tests the two latest Go releases. Deploy with 1.27.2 or later: earlier 1.27 releases carry standard library vulnerabilities (HTTP/2, net/textproto, crypto/tls) that govulncheck reports against the paths weirhttp uses. The root module builds for every target Go supports, js/wasm and wasip1/wasm included.

Two things to know before production

Strict forwarding. If your origin reads Accept-Language, a device header or a cookie to build a page, it will not see it until you say so. Key it (Key.Headers, Key.Cookies: one entry per value), allow it (Forward.Allow: the origin sees it, the key does not, so only for a header that cannot change the response), or lose it. For the first five minutes Weir counts the cookie names it dropped and logs the common ones once.

Settings that take a protection back. Forward.Mode: weir.ForwardAll returns every header to the origin unkeyed. Storable.StripSetCookie stores responses that set cookies. Freshness.DefaultStaleIfError serves stale content the origin never allowed. Each is right for some site, and each returns a risk the defaults remove. The tuning page lists all of them with what you give up and the threat ID.

Set GOMEMLIMIT (the memory store takes 40% of it) and read the production guide first.

Status and stability

Everything planned for v1 is implemented: the engine, single-range responses, targeted cache-control (RFC 9213), snapshots across restarts, the Caddy module, the Valkey store and experiment-aware keys. A pointer-light memory layout (M16-02) is deferred until a deployment exceeds the GC budget at 1M entries.

From v1.0.0 the exported Go API of the root module, store, store/memory, store/storetest, weirhttp and observe/prom follows semantic versioning. So do the metric families and labels, the Cache-Status parameter names, the Caddyfile directives and the Caddy JSON field names. Free-text values (detail and fwd strings, Event.Reason) and event kinds may gain values in a minor release, so match on them with a default case. Construct structs with field names: fields can be added in a minor release. Defaults that protect (strict forwarding, Vary handling, stale serving) change only in a major release.

Security

Weir decides what a shared cache stores and serves, so a bug can hand one user's response to another. The threat model lists 51 threats with the answer and the test for each, 7 invariants and 10 residual risks; the security page puts the residual risks in plain language. Report a vulnerability privately through the repository's Security tab (SECURITY.md).

Performance

Measured on an Apple M4 Pro (12 cores), in-process, with no network or TLS. Other hardware gives other numbers.

What Result Command
One cached hit (BenchmarkServeHitSmall, Go 1.27.2) 852 ns, 12 allocations make bench
10,000 keys expiring together, limit 32 (Go 1.27.1) origin peak in flight 32, 0 client errors make load
Origin brownout (Go 1.27.1) every request 2xx, 52,975 served stale make load

Method, raw output and what these do not show: benchmarks.

Compared with

Choose it when Choose Weir when
Varnish you want a standalone cache with VCL and years of production use the cache lives inside a Go server or Caddy and you want a concurrency budget across keys
nginx proxy_cache nginx is already your proxy and proxy_cache_lock covers your herds you need jitter, a breaker and strict forwarding
Souin, Caddy cache-handler you need its storage backends or surrogate keys today key hardening and origin protection matter more than backend choice
x/sync/singleflight, sturdyc you are deduplicating function calls, not HTTP responses you need HTTP semantics: Vary, validation, Cache-Control
CDN request collapsing you want a managed edge you need the behavior at your own origin tier, in code you control

Details and sources: compare.

Documentation

The site has the quickstart, concept pages, guides for net/http and Caddy, and the configuration reference. In this repository: the runbook for operators, the design documents, the changelog, and CONTRIBUTING.md.

License

Apache-2.0. See LICENSE and NOTICE.

Documentation

Overview

Package weir is a shared HTTP cache engine that sits between an HTTP server and an origin. It implements RFC 9111 caching and adds defenses against origin stampedes, origin and storage outages, and cache-key manipulation.

Usage

Build an Engine with New, answer each request with Engine.Serve, and call Engine.Close on shutdown. An Origin fetches what the cache cannot answer; Serve is the only path that reaches it. The zero Config is valid and picks safe defaults, including an in-process memory store.

e, err := weir.New(weir.Config{})
if err != nil {
	return err // wraps ErrInvalidConfig
}
defer e.Close(context.Background())
origin := &weirhttp.TransportOrigin{Target: originURL}
http.ListenAndServe(addr, weirhttp.Handler(e, origin))

Package weirhttp wraps an Engine for net/http, and the Caddy adapter lives in the separate module github.com/AshwinSathian/weir/caddy.

Compatibility

From v1.0.0 the exported API follows semantic versioning. Construct Config and the structs inside it with field names, never positionally, because minor releases may add fields; a new field's zero value keeps the old behavior. The store.Store interface is fixed for v1, and new storage features arrive as optional capability interfaces in package store (Scrubber, Sizer and others) that the engine finds with a type assertion. Event kinds, CacheInfo.Detail values and Event.Reason strings can also gain new values in a minor release, so match them with a default case.

The design is specified in docs/ at the repository root; start with docs/README.md. Requirement IDs such as FR-COA-4 and threat IDs such as T-19 in comments point into docs/01-technical-spec.md and docs/06-threat-model.md.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidConfig is returned by New for any invalid Config field.
	ErrInvalidConfig = errors.New("weir: invalid config")
	// ErrInvalidRequest means the request failed input validation (FR-VAL).
	// Serve returns it wrapped in *RequestError.
	ErrInvalidRequest = errors.New("weir: invalid request")
	// ErrShed means no origin slot was free within the queue budget and
	// nothing stale could be served. Serve returns it wrapped in *RetryError.
	ErrShed = errors.New("weir: origin capacity exhausted")
	// ErrCircuitOpen means the breaker is open and nothing stale could be
	// served. Serve returns it wrapped in *RetryError.
	ErrCircuitOpen = errors.New("weir: origin circuit open")
	// ErrOriginTimeout means the origin did not answer within Timeouts.Origin
	// (Timeouts.Background for background refresh and Warm).
	ErrOriginTimeout = errors.New("weir: origin timeout")
	// ErrMustRevalidate means a must-revalidate entry could not be validated.
	ErrMustRevalidate = errors.New("weir: must-revalidate response could not be validated")
	// ErrOnlyIfCached means an only-if-cached request found no usable stored
	// response.
	ErrOnlyIfCached = errors.New("weir: only-if-cached and no stored response")
	// ErrOrigin means the origin returned a transport error. Serve returns it
	// wrapped in *OriginError.
	ErrOrigin = errors.New("weir: origin error")
	// ErrClosed means the engine is closed.
	ErrClosed = errors.New("weir: engine closed")
	// ErrUpgradeNotSupported means a CONNECT or protocol upgrade request
	// reached Serve (FR-UPG-1). Adapters route those around the engine.
	ErrUpgradeNotSupported = errors.New("weir: connect and protocol upgrades not supported")
	// ErrEagerUnsupported means an eager purge wrote its epoch but the store
	// cannot scrub, so matching records were not deleted (FR-PRG-8).
	ErrEagerUnsupported = errors.New("weir: store cannot scrub; epoch written, delete skipped")
)

Sentinel errors. Compare with errors.Is; the engine returns them wrapped. StatusCode maps each to the status an adapter should send (01 §4).

Functions

func RetryAfter

func RetryAfter(err error) (time.Duration, bool)

RetryAfter returns the Retry-After hint when err classifies as shed or circuit open through a *RetryError, rounded up to whole seconds and never negative. A hint wrapped inside an *OriginError belongs to the origin and is not returned.

Example
package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/AshwinSathian/weir"
)

func main() {
	err := &weir.RetryError{Err: weir.ErrShed, After: 1500 * time.Millisecond}
	d, ok := weir.RetryAfter(err)
	fmt.Println(d, ok)

	_, ok = weir.RetryAfter(errors.New("plain"))
	fmt.Println(ok)
}
Output:
2s true
false

func StatusCode

func StatusCode(err error) int

StatusCode maps err to the status an adapter should send (01 §4). Unknown errors, and nil, map to 502.

The outermost recognized error in the chain decides, walking in the same order as errors.Is. An *OriginError maps to 502 whatever it wraps: an origin that returns weir errors (another engine) or its own context errors must not turn an origin failure into 400, 499, 503 or 504.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/AshwinSathian/weir"
)

func main() {
	fmt.Println(weir.StatusCode(nil))
	fmt.Println(weir.StatusCode(errors.New("boom")))
	fmt.Println(weir.StatusCode(weir.ErrClosed))
	// The values below depend on the error mapping in 01 §4.
	

func TrackingParams

func TrackingParams() []string

TrackingParams returns Key.QueryDrop patterns for the click and campaign identifiers that ad networks and mail tools append to links. They rarely change a response, and each distinct value would otherwise mint a key. Weir drops nothing by default (D30); opt in with

weir.Config{Key: weir.KeyConfig{QueryDrop: weir.TrackingParams()}}

Dropped parameters never reach the origin (FR-KEY-5), so leave out any name the origin reads. Each call returns a new slice that the caller may extend or trim.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"github.com/AshwinSathian/weir"
)

// newExampleOrigin counts calls and returns a response cacheable for a minute.
func newExampleOrigin(calls *atomic.Int64) weir.Origin {
	return weir.OriginFunc(func(_ context.Context, req *weir.Request) (*weir.Response, error) {
		calls.Add(1)
		return &weir.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"Cache-Control": {"max-age=60"}},
			Body:       io.NopCloser(strings.NewReader("hello " + req.Path)),
		}, nil
	})
}

func exampleGet(path string) *weir.Request {
	return &weir.Request{Method: http.MethodGet, Scheme: "http", Host: "example.com", Path: path, Header: http.Header{}}
}

func exampleClose(e *weir.Engine) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := e.Close(ctx); err != nil {
		fmt.Println("close:", err)
	}
}

func main() {
	e, err := weir.New(weir.Config{
		Key: weir.KeyConfig{QueryDrop: weir.TrackingParams()},
	})
	if err != nil {
		fmt.Println("new:", err)
		return
	}
	defer exampleClose(e)

	var calls atomic.Int64
	origin := newExampleOrigin(&calls)
	for _, q := range []string{"utm_source=a", "utm_source=b", "fbclid=xyz"} {
		req := exampleGet("/page")
		req.RawQuery = q
		resp, err := e.Serve(context.Background(), req, origin)
		if err != nil {
			fmt.Println("serve:", err)
			return
		}
		_, _ = io.Copy(io.Discard, resp.Body)
		resp.Body.Close()
	}
	fmt.Println("origin calls:", calls.Load())
}
Output:
origin calls: 1

Types

type BreakerConfig

type BreakerConfig struct {
	Window         time.Duration // 0: 10s (10 buckets)
	MinRequests    int           // 0: 20
	FailureRatio   float64       // 0: 0.5; (0, 1]
	OpenFor        time.Duration // 0: 5s
	MaxOpenFor     time.Duration // 0: max(60s, OpenFor); at least OpenFor
	HalfOpenProbes int           // 0: 1
	CountStatus500 bool
	Disable        bool
}

BreakerConfig tunes the origin circuit breaker (FR-BRK).

type BreakerState

type BreakerState uint8

BreakerState is the origin circuit breaker's state.

const (
	BreakerClosed   BreakerState = iota // requests reach the origin normally
	BreakerHalfOpen                     // a few probe requests test whether the origin recovered
	BreakerOpen                         // origin fetches fail fast until the open period ends
)

BreakerState values.

type BypassConfig

type BypassConfig struct {
	Cookies               []string
	Headers               []string      // canonicalized
	ReportStrippedCookies time.Duration // FR-OBS-5; 0: 5m; negative disables
}

BypassConfig lists request cookies and headers that skip the cache.

type CacheGroupsConfig

type CacheGroupsConfig struct{ Ignore bool }

CacheGroupsConfig controls RFC 9875 cache groups. The zero value honors them. Ignore switches off Cache-Group-Invalidation, the field with which an origin response invalidates groups (FR-INV-2); set it when parties that do not trust each other share one host (T-25). Cache-Groups is read either way, so Purge by group keeps working (FR-STO-10).

type CacheInfo

type CacheInfo struct {
	Hit       bool          // answered without contacting the origin for this request
	Fwd       FwdReason     // why the request went forward; FwdNone when Hit
	Stale     StaleReason   // non-zero when a stale response was served
	FwdStatus int           // origin status when Fwd != FwdNone (304 on revalidation)
	Stored    bool          // the forwarded response was stored
	Collapsed bool          // this request waited on another request's flight
	TTL       time.Duration // remaining freshness at send time; negative when stale
	// Detail is a short label for the Cache-Status detail parameter. Serve
	// sets it to one of two values: "negative" (a cached origin error
	// answered) or "hit-for-miss" (the URI is known to be unstorable, so the
	// request skipped coalescing). It is empty otherwise. When it is empty
	// and Stale is not StaleNone, the Cache-Status header carries the stale
	// reason instead: "stale-while-revalidate", "stale-if-error", "shed",
	// "circuit-open" or "coalesce-timeout". New values may be added in a
	// minor release, so treat an unknown value as informational.
	Detail string
}

CacheInfo describes how Serve produced a response. It feeds the Cache-Status header (FR-SRV-9) and events.

type ClientConfig

type ClientConfig struct{ HonorRevalidation bool }

ClientConfig controls client request directives. HonorRevalidation lets no-cache and max-age=0 reach the origin (D5).

type CoalesceConfig

type CoalesceConfig struct {
	LeaderMaxAge    time.Duration // 0: min(10s, Timeouts.Origin); at most Timeouts.Origin
	FollowerMaxWait time.Duration // 0: min(10s, Timeouts.Origin); at most Timeouts.Origin
	// HitForMissTTL is how long an unstorable URI is remembered so requests
	// skip coalescing. Zero means the default 30s; there is no way to set a
	// zero TTL today.
	HitForMissTTL time.Duration
}

CoalesceConfig bounds request coalescing (FR-COA).

type Config

type Config struct {
	// Store holds cached entries. Nil makes New create an in-process memory
	// store capped at 40% of GOMEMLIMIT (clamped to 16 MiB to 8 GiB), or
	// 256 MiB, with a logged warning, when GOMEMLIMIT is unset (FR-MEM-1).
	// Close closes only a store the engine created. A store you pass in is
	// yours to close, after Engine.Close returns.
	Store         store.Store
	Key           KeyConfig
	Forward       ForwardConfig
	Bypass        BypassConfig
	Storable      StorableConfig
	Freshness     FreshnessConfig
	Coalesce      CoalesceConfig
	Limiter       LimiterConfig
	Breaker       BreakerConfig
	Negative      NegativeConfig
	MissRate      MissRateConfig
	CacheGroups   CacheGroupsConfig
	Client        ClientConfig
	Timeouts      TimeoutsConfig
	Warm          WarmConfig
	Limits        LimitsConfig
	Experiments   ExperimentsConfig // zero value: no A/B bucketing (docs/10)
	CacheStatus   string            // Cache-Status member name (FR-SRV-9); "": "Weir"
	NoCacheStatus bool              // omit the Cache-Status header (T-27)
	Observer      Observer
	Logger        *slog.Logger   // nil: discard
	Rand          func() float64 // [0, 1); safe for concurrent use; nil: rand.Float64
}

Config configures an Engine (01 §6, 04 §1.1). The zero value is valid and yields the defaults. New copies it, so later changes to the caller's slices have no effect. Construct it and the structs inside it with field names, as minor releases may add fields.

Every boolean is false by default. A field named Disable turns off a whole subsystem (Breaker.Disable, Negative.Disable, MissRate.Disable), and a No* field turns off one feature inside a subsystem (NoJitter, NoEarlyRefresh, NoTraceHeaders, NoCacheStatus). Strip* and Ignore change how input is handled (Storable.StripSetCookie, CacheGroups.Ignore). The remaining booleans switch a behavior on: Key.QuerySort, Key.NormalizePath, Breaker.CountStatus500, MissRate.Throttle, Client.HonorRevalidation and Experiments.DownstreamKeysOnVariant. The one switch that is not a boolean is Bypass.ReportStrippedCookies, which a negative value turns off.

Numeric fields documented as "0: default" cannot be set to zero; zero selects the default, and a negative value is rejected with ErrInvalidConfig.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine makes shared-cache decisions per request (04 §6.1). It is safe for concurrent use (FR-LCY-3).

func New

func New(cfg Config) (*Engine, error)

New validates cfg, applies defaults and returns a ready engine. It returns an error wrapping ErrInvalidConfig for any invalid field (FR-LCY-1).

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/AshwinSathian/weir"
)

func exampleClose(e *weir.Engine) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := e.Close(ctx); err != nil {
		fmt.Println("close:", err)
	}
}

func main() {
	e, err := weir.New(weir.Config{})
	if err != nil {
		fmt.Println("new:", err)
		return
	}
	defer exampleClose(e)
	fmt.Println("engine ready")
}
Output:
engine ready

func (*Engine) Close

func (e *Engine) Close(ctx context.Context) error

Close rejects new Serve calls with ErrClosed, waits for engine goroutines until ctx is done, then cancels the rest and waits for them (FR-LCY-2). It returns ctx's error when the grace period ran out. It closes the store only if the engine created it. Later calls wait for the first until their own ctx is done and return nil or ctx's error.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"github.com/AshwinSathian/weir"
)

// newExampleOrigin counts calls and returns a response cacheable for a minute.
func newExampleOrigin(calls *atomic.Int64) weir.Origin {
	return weir.OriginFunc(func(_ context.Context, req *weir.Request) (*weir.Response, error) {
		calls.Add(1)
		return &weir.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"Cache-Control": {"max-age=60"}},
			Body:       io.NopCloser(strings.NewReader("hello " + req.Path)),
		}, nil
	})
}

func exampleGet(path string) *weir.Request {
	return &weir.Request{Method: http.MethodGet, Scheme: "http", Host: "example.com", Path: path, Header: http.Header{}}
}

func main() {
	e, err := weir.New(weir.Config{})
	if err != nil {
		fmt.Println("new:", err)
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	fmt.Println("close:", e.Close(ctx))

	var calls atomic.Int64
	_, err = e.Serve(ctx, exampleGet("/"), newExampleOrigin(&calls))
	fmt.Println("serve after close:", errors.Is(err, weir.ErrClosed), "origin calls:", calls.Load())
}
Output:
close: <nil>
serve after close: true origin calls: 0

func (*Engine) Purge added in v1.0.0

func (e *Engine) Purge(ctx context.Context, p Purge) error

Purge makes the entries p names stale (PurgeSoft) or unreachable (PurgeHard) by writing one epoch per tag (FR-PRG-1, 04 §7). Its cost is the number of tags, not the number of matching entries, which are judged at their next lookup (FR-PRG-4). All of p is validated before the first write; invalid input returns an error matching ErrInvalidRequest and purges nothing. That includes a group name no stored response can carry: one longer than Limits.MaxGroupBytes or with a byte outside 0x20 to 0x7E (FR-STO-10). A store error stops the call and is returned: epochs already written stay, EvPurge is still emitted for them, and repeating the call is safe; with Eager, the tags written before the failure are not scrubbed. The memory store refuses a hard purge of a new URL or group past its MaxHardEpochs with an error matching store.ErrUnavailable (05 E-6); purge with All instead. Such a refusal does not count toward the store breaker.

A purge shows at the next lookup. With the memory store a soft purge by URL or group is timed to the next whole second (05 E-7), so its stale-while-revalidate and stale-if-error windows can run up to 1 s long, and a refresh sent in that second may be repeated once.

Eager with PurgeSoft is invalid input. Eager with PurgeHard writes its epochs, then deletes the matching records when the store implements store.Scrubber (FR-PRG-8); otherwise it returns ErrEagerUnsupported with the epochs in place. A scrub error is returned too, and the entries stay unreachable.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"github.com/AshwinSathian/weir"
)

// newExampleOrigin counts calls and returns a response cacheable for a minute.
func newExampleOrigin(calls *atomic.Int64) weir.Origin {
	return weir.OriginFunc(func(_ context.Context, req *weir.Request) (*weir.Response, error) {
		calls.Add(1)
		return &weir.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"Cache-Control": {"max-age=60"}},
			Body:       io.NopCloser(strings.NewReader("hello " + req.Path)),
		}, nil
	})
}

func exampleGet(path string) *weir.Request {
	return &weir.Request{Method: http.MethodGet, Scheme: "http", Host: "example.com", Path: path, Header: http.Header{}}
}

func exampleClose(e *weir.Engine) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := e.Close(ctx); err != nil {
		fmt.Println("close:", err)
	}
}

func main() {
	e, err := weir.New(weir.Config{})
	if err != nil {
		fmt.Println("new:", err)
		return
	}
	defer exampleClose(e)

	var calls atomic.Int64
	origin := newExampleOrigin(&calls)
	fetch := func() {
		resp, err := e.Serve(context.Background(), exampleGet("/page"), origin)
		if err != nil {
			fmt.Println("serve:", err)
			return
		}
		_, _ = io.Copy(io.Discard, resp.Body)
		resp.Body.Close()
	}
	fetch()
	fetch()
	fmt.Println("calls before purge:", calls.Load())

	err = e.Purge(context.Background(), weir.Purge{
		Mode: weir.PurgeHard,
		URLs: []string{"http://example.com/page"},
	})
	if err != nil {
		fmt.Println("purge:", err)
		return
	}
	fetch()
	fmt.Println("calls after purge:", calls.Load())
}
Output:
calls before purge: 1
calls after purge: 2

func (*Engine) Serve

func (e *Engine) Serve(ctx context.Context, req *Request, origin Origin) (*Response, error)

Serve answers one request. It never returns (nil, nil). On a nil error the caller owns resp.Body and must close it. A non-nil error means no response could be produced; StatusCode(err) gives the status an adapter should send.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"github.com/AshwinSathian/weir"
)

// newExampleOrigin counts calls and returns a response cacheable for a minute.
func newExampleOrigin(calls *atomic.Int64) weir.Origin {
	return weir.OriginFunc(func(_ context.Context, req *weir.Request) (*weir.Response, error) {
		calls.Add(1)
		return &weir.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"Cache-Control": {"max-age=60"}},
			Body:       io.NopCloser(strings.NewReader("hello " + req.Path)),
		}, nil
	})
}

func exampleGet(path string) *weir.Request {
	return &weir.Request{Method: http.MethodGet, Scheme: "http", Host: "example.com", Path: path, Header: http.Header{}}
}

func exampleClose(e *weir.Engine) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := e.Close(ctx); err != nil {
		fmt.Println("close:", err)
	}
}

func main() {
	e, err := weir.New(weir.Config{})
	if err != nil {
		fmt.Println("new:", err)
		return
	}
	defer exampleClose(e)

	var calls atomic.Int64
	origin := newExampleOrigin(&calls)
	for range 2 {
		resp, err := e.Serve(context.Background(), exampleGet("/page"), origin)
		if err != nil {
			fmt.Println("serve:", err)
			return
		}
		body, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		fmt.Printf("%d %q hit=%v\n", resp.StatusCode, body, resp.Cache.Hit)
	}
	fmt.Println("origin calls:", calls.Load())
}
Output:
200 "hello /page" hit=false
200 "hello /page" hit=true
origin calls: 1

func (*Engine) SetMode

func (e *Engine) SetMode(m Mode, ttl time.Duration) error

SetMode switches the incident mode for ttl, after which it reverts to ModeNormal (FR-MODE-1, D33). ttl must be in (0, 24 h]. Modes are not persisted. It returns an error wrapping ErrInvalidConfig for a bad mode or ttl. Expiry is noticed, and its EvMode emitted, by the first Serve after the ttl runs out.

func (*Engine) Stats added in v1.0.0

func (e *Engine) Stats() EngineStats

Stats returns point-in-time gauges for exporters: in-flight origin fetches, queued fetches, breaker state, and store bytes when the store reports them (04 §9.2). Both limiter pools count (FR-LIM-7). It only reads: an open breaker whose period has ended reports half-open without moving, so Stats never calls the Observer (FR-OBS-2) and is safe inside a collector's lock.

func (*Engine) Warm

func (e *Engine) Warm(ctx context.Context, reqs iter.Seq[*Request], origin Origin) (WarmStats, error)

Warm fetches each request through the normal path at background priority and stores what is storable (FR-WRM-1, T6.4). Warm.Concurrency workers fetch at once; each waits for a limiter slot outside the foreground reserve until ctx ends. A request with a fresh entry, or whose key another request's flight fetches and stores, counts as skipped (FR-WRM-2). Requests Weir would never store (unsafe methods, Range, only-if-cached) are not sent and count as not stored. Warm stops at the first ctx cancellation and returns ctx's error. Once Close starts, it takes no new request, lets running fetches finish within Close's grace, and returns ErrClosed.

With experiments configured, Warm stores the control variant (or the winner) of each matching experiment whatever Cookie the request carries (docs/10 §4); other variants fill on demand.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"slices"
	"strings"
	"sync/atomic"
	"time"

	"github.com/AshwinSathian/weir"
)

// newExampleOrigin counts calls and returns a response cacheable for a minute.
func newExampleOrigin(calls *atomic.Int64) weir.Origin {
	return weir.OriginFunc(func(_ context.Context, req *weir.Request) (*weir.Response, error) {
		calls.Add(1)
		return &weir.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"Cache-Control": {"max-age=60"}},
			Body:       io.NopCloser(strings.NewReader("hello " + req.Path)),
		}, nil
	})
}

func exampleGet(path string) *weir.Request {
	return &weir.Request{Method: http.MethodGet, Scheme: "http", Host: "example.com", Path: path, Header: http.Header{}}
}

func exampleClose(e *weir.Engine) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := e.Close(ctx); err != nil {
		fmt.Println("close:", err)
	}
}

func main() {
	e, err := weir.New(weir.Config{})
	if err != nil {
		fmt.Println("new:", err)
		return
	}
	defer exampleClose(e)

	var calls atomic.Int64
	origin := newExampleOrigin(&calls)
	reqs := slices.Values([]*weir.Request{exampleGet("/a"), exampleGet("/b")})

	st, err := e.Warm(context.Background(), reqs, origin)
	fmt.Printf("%+v %v\n", st, err)
	st, err = e.Warm(context.Background(), reqs, origin)
	fmt.Printf("%+v %v\n", st, err)
}
Output:
{Fetched:2 Skipped:0 NotStored:0 Failed:0} <nil>
{Fetched:0 Skipped:2 NotStored:0 Failed:0} <nil>

type EngineStats

type EngineStats struct {
	Inflight     int          // origin fetches holding limiter slots
	Queued       int          // fetches waiting for a slot
	BreakerState BreakerState // BreakerClosed, BreakerHalfOpen or BreakerOpen
	StoreBytes   int64        // -1 when the store does not implement store.Sizer
}

EngineStats is a point-in-time snapshot for gauges, polled by exporters.

type Event

type Event struct {
	Kind      EventKind
	Time      time.Time
	Partition string        // truncated to 256 bytes; empty for engine-wide events; never a metric label
	Duration  time.Duration // fetch, wait, or open duration where relevant
	Status    int
	Reason    string    // fixed vocabulary per kind, safe as a metric label
	Info      CacheInfo // for EvRequest
	// Set only on EvExposure. Experiment and Variant are operator
	// configuration. Visitor is a 16-character hex prefix of SHA-256 of the
	// visitor id.
	Experiment string // experiment name, from configuration
	Variant    string // variant name, from configuration
	Visitor    string // 16 hex characters; unbounded, never a metric label
}

Event is one engine event. Reason uses a fixed vocabulary per Kind and never contains request data.

type EventKind

type EventKind uint8

EventKind identifies an event. Its String form is safe as a metric label.

const (
	EvRequest         EventKind = iota + 1 // every Serve return
	EvFetchStart                           // before every Origin.Fetch
	EvFetchEnd                             // after every Origin.Fetch
	EvCoalesceJoin                         // a request joined an existing flight
	EvCoalesceTimeout                      // a wait on a flight expired and the request stopped waiting
	EvShed                                 // the limiter refused
	EvStaleServed                          // a stale response was served
	EvRefreshDropped                       // a background refresh was not started
	EvBreakerState                         // breaker transition
	EvStoreError                           // the store guard saw store.ErrUnavailable
	EvStoreBreaker                         // the store guard opened or closed
	EvKeyRejected                          // request validation failed
	EvNotStored                            // storability failed
	EvVaryOverflow                         // the variant cap was reached
	EvNegativeServed                       // a negative entry was used
	EvPurge                                // Purge or invalidation wrote epochs
	EvMissRateAnomaly                      // a window closed with an anomalous partition
	EvEvict                                // the memory store evicted
	EvMode                                 // the incident mode changed (FR-MODE-1)
	EvExposure                             // a visitor was first seen in an experiment variant (docs/10 E1)

)

EventKind values (04 §9.2).

func (EventKind) String

func (k EventKind) String() string

String returns the kind's label name, or "unknown".

type ExperimentConfig added in v1.0.0

type ExperimentConfig struct {
	Name     string
	Salt     string // decorrelates experiments; change it to reshuffle visitors
	Winner   string // a variant name ends the experiment for all visitors; "" keeps it running
	Host     string // normalized, lower case; "" matches any host
	Path     string // exact path, or a prefix when it ends in "*"; "" matches any
	Variants []VariantConfig
}

ExperimentConfig is one static experiment. The first variant is the control arm, served when consent is missing.

type ExperimentConsent added in v1.0.0

type ExperimentConsent struct {
	Cookie string // consent cookie name, for example "cmp_consent"
	Value  string // an element of its value, for example "analytics=yes"; elements are split on '&', '|' and ','
}

ExperimentConsent gates bucketing (E6). With Cookie set and the cookie absent or lacking Value, a visitor gets the control variant, no cookie is set and no exposure is recorded. With the zero value, visitors are bucketed without a gate and New logs a warning.

type ExperimentsConfig added in v1.0.0

type ExperimentsConfig struct {
	Cookie string // assignment cookie name; "": "weir_v"
	// SigningKeys sign and verify the assignment cookie: 1 to 4 keys of 32
	// to 256 bytes each. The first signs and all verify, which allows
	// rotation. New returns an error wrapping ErrInvalidConfig for any other
	// count or length, and when Experiments is set without a key.
	SigningKeys [][]byte
	Header      string // forwarded variant header; "": "Weir-Variant"; no underscore
	MaxActive   int    // experiments that may apply to one request; 0: 4; at most 8
	Consent     ExperimentConsent
	Experiments []ExperimentConfig

	// DownstreamKeysOnVariant declares that every cache in front of Weir keys
	// on the variant (Weir-Variant, or the experiment cookie). Only then do
	// matched responses keep public, s-maxage and CDN-Cache-Control, except
	// the one that sets the assignment cookie; by default all are rewritten
	// to private (E7, T-36).
	DownstreamKeysOnVariant bool
}

ExperimentsConfig turns on A/B bucketing at the cache (docs/10 §3). The zero value turns it off. Each matching experiment adds a (experiment, variant) dimension to the cache key and sets Header on the forwarded request, so the origin sees exactly what was keyed (INV-1). A client's own Header is always discarded (T-35).

type ForwardConfig

type ForwardConfig struct {
	Mode           ForwardMode
	Allow          []string // canonicalized; added to the trace headers
	NoTraceHeaders bool     // do not forward traceparent, tracestate, X-Request-Id (they are forwarded by default)
}

ForwardConfig controls which request headers reach the origin (FR-FWD).

type ForwardMode

type ForwardMode uint8

ForwardMode selects the forwarding policy.

const (
	ForwardStrict ForwardMode = iota
	ForwardAll                // logs a warning at New
)

ForwardMode values.

type FreshnessConfig

type FreshnessConfig struct {
	Jitter            float64 // 0: 0.10 unless NoJitter; [0, 0.5]
	NoJitter          bool
	JitterMinLifetime time.Duration // 0: 10s
	EarlyRefreshBeta  float64       // 0: 1.0
	NoEarlyRefresh    bool
	// HeuristicFraction is the share of the time since Last-Modified used as
	// heuristic freshness. Zero means the default 0.1; there is no way to
	// set zero today (use Freshness.HeuristicMax to bound the result).
	HeuristicFraction           float64
	HeuristicMax                time.Duration // 0: 1h
	DefaultTTL                  time.Duration // 0: none
	DefaultStaleWhileRevalidate time.Duration // 0: none
	DefaultStaleIfError         time.Duration // 0: none
	Keep                        time.Duration // 0: 5m; only entries with a validator
}

FreshnessConfig tunes lifetimes, jitter and early refresh (FR-FRS).

type FwdReason

type FwdReason uint8

FwdReason is why a request went to the origin. Values mirror the fwd parameter of RFC 9211 §2.2.

const (
	FwdNone     FwdReason = iota // not forwarded
	FwdBypass                    // a bypass rule matched (fwd=bypass)
	FwdMethod                    // the method is not cacheable (fwd=method)
	FwdURIMiss                   // no stored response for the URI (fwd=uri-miss)
	FwdVaryMiss                  // stored responses exist, none match Vary (fwd=vary-miss)
	FwdRequest                   // the request's directives forced forwarding (fwd=request)
	FwdStale                     // the stored response was stale (fwd=stale)
)

FwdReason values.

type KeyConfig

type KeyConfig struct {
	QueryDrop      []string // exact names or "prefix*"
	QueryKeep      []string // allowlist; empty means keep all
	QuerySort      bool
	NormalizePath  bool
	Headers        []string // canonicalized with http.CanonicalHeaderKey
	Cookies        []string // order is the forwarded order
	Vary           VaryMode
	VaryAllow      []string // canonicalized; required for sensitive Vary names
	MaxVaryHeaders int      // 0: 8
	MaxVariants    int      // 0: 8
	AcceptEncoding []string // empty: ["gzip"]; lowercased
}

KeyConfig controls which request parts form the cache key (FR-KEY).

type LimiterConfig

type LimiterConfig struct {
	MaxConcurrent int // 0: 64
	// MaxQueue is the number of fetches that may wait for a slot. Zero means
	// the default 1024; there is no way to set a zero-length queue today.
	MaxQueue        int
	MaxQueueWait    time.Duration // 0: 2s
	MaxPerPartition int           // 0: 16; clamped to MaxConcurrent
	// ReserveForeground is the number of slots background refresh and Warm
	// may not use. Zero means MaxConcurrent/4, at least 1 when MaxConcurrent
	// is 2 or more; there is no way to reserve none then. It must stay below
	// MaxConcurrent.
	ReserveForeground int
	MaxPerHost        int // 0: off (FR-FAIR-1); caps in-flight fetches per normalized host, in each pool
	MaxUpload         int // 0: MaxConcurrent/4, at least 1; requests with a body use this separate pool (D25)
}

LimiterConfig bounds origin concurrency (FR-LIM).

type LimitsConfig

type LimitsConfig struct {
	MaxPathBytes        int // 0: 8192
	MaxQueryBytes       int // 0: 8192
	MaxQueryParams      int // 0: 256
	MaxKeyedHeaderBytes int // 0: 1024
	MaxGroups           int // 0: 32
	MaxGroupBytes       int // 0: 128
}

LimitsConfig bounds request input (FR-VAL-1, FR-VAL-3, FR-STO-10).

type MissRateConfig

type MissRateConfig struct {
	Window    time.Duration // 0: 10s
	TopK      int           // 0: 64
	MinMisses int           // 0: 500
	MinRatio  float64       // 0: 0.9; at most 1
	// Throttle caps an anomalous partition at one origin fetch for the
	// following window (FR-MR-3). The tracker cannot tell a flood from a
	// busy path whose responses are never stored or always revalidated
	// (no-store, no-cache, max-age=0, Authorization or Range traffic): past MinMisses such a path is throttled too, and
	// most of its requests are then shed. Run with Throttle off first:
	// every partition the warning names would be capped. Give paths known
	// to be uncacheable a bypass rule; bypassed requests are not counted.
	// No effect with Disable.
	Throttle bool
	Disable  bool
}

MissRateConfig tunes the miss-rate detector (FR-MR-*).

type Mode

type Mode uint8

Mode is an incident mode set with Engine.SetMode (FR-MODE-1, D33).

const (
	ModeNormal       Mode = iota
	ModeStaleOnError      // serve stale on any error, up to 24 h (FR-MODE-2)
	ModeBypass            // pass every request through, store nothing (FR-MODE-3)
)

Mode values.

type NegativeConfig

type NegativeConfig struct {
	TTL     time.Duration // 0: 2s
	Disable bool
}

NegativeConfig tunes negative caching of origin errors (FR-NEG).

type Observer

type Observer interface{ Observe(Event) }

Observer receives engine events (04 §9). Observe is called synchronously on the request path, so implementations must be fast, must not block, must not panic and must be safe for concurrent use. The engine does not recover an Observer panic: it ends the request or flight it ran in, and the origin response that call held is closed only when the garbage collector finalizes its connection.

type Origin

type Origin interface {
	Fetch(ctx context.Context, req *Request) (*Response, error)
}

Origin produces responses for forwarded requests. Fetch must honor ctx: return promptly once ctx is done, and make body reads fail after that. Close depends on it (FR-LCY-2). Fetch may be called after the request that triggered it has finished (background refresh), and concurrently.

type OriginError

type OriginError struct {
	Err error
}

OriginError wraps a transport error from Origin.Fetch. It matches ErrOrigin under errors.Is and unwraps to Err.

func (*OriginError) Error

func (e *OriginError) Error() string

Error implements error.

func (*OriginError) Is

func (e *OriginError) Is(target error) bool

Is reports whether target is ErrOrigin.

func (*OriginError) Unwrap

func (e *OriginError) Unwrap() error

Unwrap returns the origin's error.

type OriginFunc

type OriginFunc func(ctx context.Context, req *Request) (*Response, error)

OriginFunc adapts a function to the Origin interface.

func (OriginFunc) Fetch

func (f OriginFunc) Fetch(ctx context.Context, req *Request) (*Response, error)

Fetch calls f.

type Purge

type Purge struct {
	Mode   PurgeMode // PurgeSoft (zero value) or PurgeHard
	All    bool
	URLs   []string // absolute http(s) URLs; parsed and rewritten exactly like requests
	Origin string   // "scheme://host[:port]"; required when Groups is non-empty
	Groups []string
	Eager  bool // with PurgeHard, also delete matching records now, if the store implements store.Scrubber
}

Purge describes what Engine.Purge invalidates (FR-PRG-1). Invalid input rejects the whole call before any epoch is written.

type PurgeMode

type PurgeMode uint8

PurgeMode selects how purged entries are treated.

const (
	PurgeSoft PurgeMode = iota // entries become stale and may still be served stale
	PurgeHard                  // entries become unreachable
)

PurgeMode values.

type Request

type Request struct {
	Method   string        // exactly as received; methods are case-sensitive
	Scheme   string        // "http" or "https"
	Host     string        // authority (host[:port]) the client addressed
	Path     string        // origin-form path, still percent-encoded, as received
	RawQuery string        // query without the leading '?', as received
	Header   http.Header   // never mutated by Weir
	Body     io.ReadCloser // nil for GET and HEAD; passed through for other methods
}

Request is one client request as the adapter received it (01 §4).

type RequestError

type RequestError struct {
	Reason string
}

RequestError reports why a request failed validation. It matches ErrInvalidRequest under errors.Is.

func (*RequestError) Error

func (e *RequestError) Error() string

Error implements error.

func (*RequestError) Unwrap

func (e *RequestError) Unwrap() error

Unwrap returns ErrInvalidRequest.

type Response

type Response struct {
	StatusCode int
	Header     http.Header
	Body       io.ReadCloser // never nil on responses returned by Serve
	// Cache describes how Serve produced the response. Serve sets it; an
	// Origin's value is ignored.
	Cache CacheInfo
}

Response is a response from an Origin or from Serve. The receiver of a Response from Serve owns Body and must close it.

type RetryError

type RetryError struct {
	Err   error         // ErrShed or ErrCircuitOpen
	After time.Duration // RetryAfter rounds it up to whole seconds
}

RetryError carries a Retry-After hint for shed and circuit-open errors.

func (*RetryError) Error

func (e *RetryError) Error() string

Error implements error.

func (*RetryError) Unwrap

func (e *RetryError) Unwrap() error

Unwrap returns Err.

type StaleReason

type StaleReason uint8

StaleReason is why a stale response was served.

const (
	StaleNone            StaleReason = iota // the response was not stale
	StaleWhileRevalidate                    // within stale-while-revalidate
	StaleIfError                            // the origin failed, within stale-if-error
	StaleShed                               // the limiter shed the fetch
	StaleCircuitOpen                        // the breaker was open
	StaleCoalesceTimeout                    // the wait on another request's flight timed out
)

StaleReason values.

type StorableConfig

type StorableConfig struct {
	Statuses       []int // nil: default set; never 206, 304, 500, 502, 503, 504
	MaxObjectBytes int64 // 0: 1 MiB, body plus headers
	StripSetCookie bool
	StreamTypes    []string // Content-Type media types streamed like text/event-stream (FR-STR-1)
}

StorableConfig limits what may be stored (FR-STO).

type TimeoutsConfig

type TimeoutsConfig struct {
	Origin     time.Duration // 0: 30s
	Background time.Duration // 0: 30s; background refresh and Warm (FR-TMO-1)
	Store      time.Duration // 0: 50ms (remote stores only)
	StreamIdle time.Duration // 0: 60s; bounds a streamed body by idle time, not total time (D26)
}

TimeoutsConfig bounds origin and store calls (FR-TMO).

type VariantConfig added in v1.0.0

type VariantConfig struct {
	Name   string
	Weight int
}

VariantConfig is one arm of an experiment. Weight is relative to the other arms.

type VaryMode

type VaryMode uint8

VaryMode selects how response Vary headers are handled.

const (
	VaryAuto VaryMode = iota
	VaryStrict
)

VaryMode values.

type WarmConfig

type WarmConfig struct{ Concurrency int } // 0: min(4, MaxConcurrent-ReserveForeground); at most that

WarmConfig tunes Engine.Warm.

type WarmStats

type WarmStats struct{ Fetched, Skipped, NotStored, Failed int }

WarmStats counts the outcome of a warm call per URL.

Directories

Path Synopsis
caddy module
examples
weirproxy command
Command weirproxy is a caching reverse proxy built from weir and weirhttp with default settings:
Command weirproxy is a caching reverse proxy built from weir and weirhttp with default settings:
internal
breaker
Package breaker is the engine's origin circuit breaker: a failure ratio over a rolling window with a minimum volume, counting gateway failures only (docs/04-lld.md §8.3, docs/02-architecture.md ADR-7).
Package breaker is the engine's origin circuit breaker: a failure ratio over a rolling window with a minimum volume, counting gateway failures only (docs/04-lld.md §8.3, docs/02-architecture.md ADR-7).
coalesce
Package coalesce holds the flight table that lets concurrent misses for one key share a single origin fetch (docs/04-lld.md §8.1, docs/03-hld.md §3.3).
Package coalesce holds the flight table that lets concurrent misses for one key share a single origin fetch (docs/04-lld.md §8.1, docs/03-hld.md §3.3).
experiments
Package experiments holds the parts of A/B experiment bucketing that need no engine: the configuration and its load-time checks, the signed visitor cookie, and the deterministic variant pick (docs/10-experiments-spec.md §2 to §4, threat T-35).
Package experiments holds the parts of A/B experiment bucketing that need no engine: the configuration and its load-time checks, the signed visitor cookie, and the deterministic variant pick (docs/10-experiments-spec.md §2 to §4, threat T-35).
httpcc
Package httpcc parses and evaluates HTTP caching fields (RFC 9111): Cache-Control directives, lifetimes and entry evaluation (docs/04-lld.md §4).
Package httpcc parses and evaluates HTTP caching fields (RFC 9111): Cache-Control directives, lifetimes and entry evaluation (docs/04-lld.md §4).
keys
Package keys validates requests, normalizes the inputs that reach the cache key and rewrites the forwarded request to match (docs/04-lld.md §3).
Package keys validates requests, normalizes the inputs that reach the cache key and rewrites the forwarded request to match (docs/04-lld.md §3).
limiter
Package limiter bounds concurrent origin fetches: a global cap, a cap per partition, a foreground reserve and a bounded FIFO queue (docs/04-lld.md §8.2, docs/02-architecture.md ADR-8).
Package limiter bounds concurrent origin fetches: a global cap, a cap per partition, a foreground reserve and a bounded FIFO queue (docs/04-lld.md §8.2, docs/02-architecture.md ADR-8).
missrate
Package missrate finds partitions whose requests are nearly all misses: a Space-Saving summary of requests and misses per partition over a fixed window, in O(TopK) memory whatever the number of partitions (docs/04-lld.md §8.4, docs/02-architecture.md ADR-9).
Package missrate finds partitions whose requests are nearly all misses: a Space-Saving summary of requests and misses per partition over a fixed window, in O(TopK) memory whatever the number of partitions (docs/04-lld.md §8.4, docs/02-architecture.md ADR-9).
sfv
Package sfv parses the HTTP structured fields (RFC 9651) that Weir reads from origin responses: the List of Strings carried by Cache-Groups and Cache-Group-Invalidation (docs/04-lld.md §8.5), and the Dictionary of targeted cache control fields such as CDN-Cache-Control (§13.2).
Package sfv parses the HTTP structured fields (RFC 9651) that Weir reads from origin responses: the List of Strings carried by Cache-Groups and Cache-Group-Invalidation (docs/04-lld.md §8.5), and the Dictionary of targeted cache control fields such as CDN-Cache-Control (§13.2).
testorigin
Package testorigin is a programmable weir.Origin for engine and load tests (docs/07-testing-strategy.md §3).
Package testorigin is a programmable weir.Origin for engine and load tests (docs/07-testing-strategy.md §3).
Package loadtest holds the real-time load and adversarial scenarios of docs/07-testing-strategy.md §9.
Package loadtest holds the real-time load and adversarial scenarios of docs/07-testing-strategy.md §9.
observe
prom module
Package store defines the storage contract the engine caches through: the Store interface, the Entry record, purge epochs and the optional capability interfaces.
Package store defines the storage contract the engine caches through: the Store interface, the Entry record, purge epochs and the optional capability interfaces.
memory
Package memory is the default in-process store: sharded, byte-weighted S3-FIFO (ADR-6).
Package memory is the default in-process store: sharded, byte-weighted S3-FIFO (ADR-6).
storetest
Package storetest is the conformance suite for store.Store implementations (docs/05-storage-interface-spec.md section 8).
Package storetest is the conformance suite for store.Store implementations (docs/05-storage-interface-spec.md section 8).
valkey module
Package weirhttp adapts a weir.Engine to net/http: middleware and a handler that serve requests through the engine, conversions between net/http and weir types, and an Origin backed by an http.RoundTripper.
Package weirhttp adapts a weir.Engine to net/http: middleware and a handler that serve requests through the engine, conversions between net/http and weir types, and an Origin backed by an http.RoundTripper.

Jump to

Keyboard shortcuts

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