Documentation
¶
Overview ¶
Package cache implements a two-tier HTTP response cache for anzu-proxy.
The memory tier uses an LRU with a configurable byte budget. The optional disk tier shards entries across 256 directories by hash prefix, writes atomically, and persists across restarts. Hot disk hits are promoted back to memory. Both tiers evict by LRU.
On-disk format (one file per cached entry):
[4] magic "ANZU" [2] format version (BE uint16) [4] status code (BE uint32) [8] expires_at unix seconds (BE int64) [4] header bytes length (BE uint32) [N] header bytes (JSON of http.Header) [8] body length (BE uint64) [N] body bytes [32] SHA-256 of everything above
The trailing checksum makes corrupted files detectable; bad files are silently dropped and the upstream is re-fetched.
Index ¶
- func BytesFlag(name string, defaultVal int64, usage string) *int64
- func CacheKey(method, rawURL string) string
- func DefaultCacheDir() (string, string, error)
- func DrainAndCache(cache *ResponseCache, key string, resp *http.Response, ttl time.Duration) io.ReadCloser
- func IsCacheable(resp *http.Response) bool
- func IsCacheableMethod(method string) bool
- func ShouldCacheExchange(req *http.Request, resp *http.Response) bool
- func TTLFromResponse(resp *http.Response, defaultTTL time.Duration) time.Duration
- type CacheConfig
- type CacheEntry
- type CacheLocation
- type ResponseCache
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BytesFlag ¶
bytesFlag is a convenience wrapper to register a bytesValue with the stdlib `flag` package, mirroring `flag.Int64`'s API.
func CacheKey ¶
CacheKey builds a canonical cache key from method + URL. When the disk tier is enabled, this string is SHA-256'd to form the on-disk filename — but in-memory we use the raw key for direct map lookup.
func DefaultCacheDir ¶
defaultCacheDir picks a sensible disk-cache location when the user enables the disk tier but doesn't pass -cache-dir explicitly.
The strategy mirrors how well-behaved Linux daemons choose paths:
- If we look like we're running as root in a snap, use $SNAP_COMMON.
- Otherwise, if running as root: /var/cache/anzu-proxy (FHS).
- Else if $XDG_CACHE_HOME is set: $XDG_CACHE_HOME/anzu-proxy.
- Else if $HOME is set: $HOME/.cache/anzu-proxy.
- Last resort: $TMPDIR/anzu-proxy-cache (with a warning — this loses cache data on /tmp cleanup).
The chosen path is mkdir'd and returned; an error is only returned if every fallback fails to create a writable directory.
func DrainAndCache ¶
func DrainAndCache(cache *ResponseCache, key string, resp *http.Response, ttl time.Duration) io.ReadCloser
returns a fresh ReadCloser the caller may use to forward the body.
func IsCacheable ¶
IsCacheable reports whether an HTTP response may be stored, looking only at the response. Callers that have the originating request should use ShouldCacheExchange, which additionally refuses to cache responses to credentialed requests.
Method-level filtering (GET, HEAD, OPTIONS) is handled by isCacheableMethod at the call site in http_handler.go, keeping this function focused on response attributes only.
func IsCacheableMethod ¶
isCacheableMethod reports whether method is one whose responses may be stored in a shared cache (GET, HEAD, OPTIONS per RFC 7234 §3).
func ShouldCacheExchange ¶
ShouldCacheExchange decides whether a (request, response) pair may be stored. It applies IsCacheable(resp) first and then refuses to store responses to credentialed requests (Authorization or Cookie present) unless the origin explicitly opted in with Cache-Control: public.
Background: with -htpasswd multi-user mode, the cache key is shared across users (sha256(method+url)). Without this guard, user A's authenticated GET would be served from cache to user B — a cross-user data leak. RFC 7234 §3.2 and §5.2.2.5 require the same behaviour for shared caches.
Types ¶
type CacheConfig ¶
type CacheConfig struct {
MaxMemoryBytes int64 // 0 = unlimited (still bounded by MaxEntries)
MaxDiskBytes int64 // 0 = no disk tier
MaxObjectBytes int64 // 0 = unlimited per object
MaxEntries int // hard cap on entry count across both tiers (0 = unlimited)
DiskDir string // disk cache root; created if absent. Empty = no disk tier.
TTL time.Duration // default TTL when origin doesn't say
}
CacheConfig configures a ResponseCache.
type CacheEntry ¶
CacheEntry holds one cached HTTP response.
func (*CacheEntry) IsExpired ¶
func (e *CacheEntry) IsExpired() bool
IsExpired reports whether this entry is past its TTL.
type CacheLocation ¶
type CacheLocation int
CacheLocation tells us where an indexed entry currently lives.
type ResponseCache ¶
type ResponseCache struct {
// Default TTL when origin doesn't specify Cache-Control.
TTL time.Duration
// contains filtered or unexported fields
}
ResponseCache is the public type used by the HTTP handlers.
func NewResponseCache ¶
func NewResponseCache(cfg CacheConfig) (*ResponseCache, error)
NewResponseCache creates a configured cache. If cfg.DiskDir is non-empty, it is created (mkdir -p) and any existing on-disk state is loaded into the index.
func (*ResponseCache) Close ¶
func (c *ResponseCache) Close()
Close stops background workers. Safe to call multiple times.
func (*ResponseCache) Get ¶
func (c *ResponseCache) Get(key string) *CacheEntry
Get retrieves an entry from either tier. A disk hit is promoted into the memory tier so subsequent hits are RAM-fast.
func (*ResponseCache) Set ¶
func (c *ResponseCache) Set(key string, entry *CacheEntry)
Set inserts or replaces an entry. Objects larger than MaxObjectBytes are dropped silently — caller continues to forward the response uncached.
func (*ResponseCache) Stats ¶
func (c *ResponseCache) Stats() (entries int, memBytes, diskBytes int64)
Stats returns counters useful for monitoring.