Documentation
¶
Overview ¶
Package cache is part of the GoFastr framework. See https://github.com/DonaldMurillo/gofastr for documentation.
Index ¶
- Constants
- Variables
- func CacheMiddleware(cache Cache, ttl time.Duration) func(http.Handler) http.Handler
- func CacheMiddlewareWithLimit(cache Cache, ttl time.Duration, maxBodyBytes int) func(http.Handler) http.Handler
- func GetOrSet(ctx context.Context, c Cache, key string, ttl time.Duration, dest any, ...) error
- type Battery
- type Cache
- type Item
- type KeyScanner
- type Loader
- type MemoryCache
- func (mc *MemoryCache) Clear(_ context.Context) error
- func (mc *MemoryCache) Close()
- func (mc *MemoryCache) Delete(_ context.Context, key string) error
- func (mc *MemoryCache) Exists(_ context.Context, key string) (bool, error)
- func (mc *MemoryCache) Get(_ context.Context, key string, dest any) error
- func (mc *MemoryCache) Len() int
- func (mc *MemoryCache) Set(_ context.Context, key string, value any, ttl time.Duration) error
- type Option
- type RedisCache
- func (rc *RedisCache) Clear(ctx context.Context) error
- func (rc *RedisCache) Delete(ctx context.Context, key string) error
- func (rc *RedisCache) Exists(ctx context.Context, key string) (bool, error)
- func (rc *RedisCache) Get(ctx context.Context, key string, dest any) error
- func (rc *RedisCache) Set(ctx context.Context, key string, value any, ttl time.Duration) error
- type RedisClient
Constants ¶
const DefaultMaxCacheableBytes = 8 << 20
CacheMiddleware returns an HTTP middleware that caches GET responses using the provided Cache implementation. The X-Cache response header is set to "HIT" or "MISS" accordingly.
The middleware is conservative and refuses to cache responses that carry user-specific data or that the origin marks as uncacheable:
- Requests other than GET are passed through untouched.
- Requests carrying a Range header are passed through untouched and never cached: the key does not encode the Range, so a partial (206) body must not be allowed to collide with the full variant.
- The cache key includes the request authority (Host) so distinct virtual hosts sharing one Cache do not serve each other's content.
- Requests with Authorization or Cookie headers are not served from or written to the cache by default (RFC 9111 §3.5 / §3 default).
- Requests with Cache-Control: no-cache or no-store bypass the stored variant and force a fresh origin fetch (no-store also skips writing the response back into the cache).
- Responses with Set-Cookie or with Cache-Control containing private, no-store, or no-cache are never stored.
- Responses with non-2xx/3xx status (i.e. 4xx and 5xx) are never stored. Partial-content (206) responses, or any response carrying a Content-Range header, are likewise never stored.
- Responses carrying a Vary header are stored under a key that includes the values of every listed request header so different variants do not collide. A Vary: * response is treated as uncacheable and is never stored (RFC 9111 §4.1).
DefaultMaxCacheableBytes caps how large a response CacheMiddleware will buffer for caching (8 MiB). Larger responses stream straight to the client and are not cached, so a single huge response can't pin unbounded memory.
Variables ¶
var ErrCacheMiss = errors.New("cache: key not found")
ErrCacheMiss is returned when a key is not found in the cache.
Functions ¶
func CacheMiddleware ¶
func CacheMiddlewareWithLimit ¶
func CacheMiddlewareWithLimit(cache Cache, ttl time.Duration, maxBodyBytes int) func(http.Handler) http.Handler
CacheMiddlewareWithLimit is CacheMiddleware with an explicit cap on the response size that may be buffered for caching. A response exceeding maxBodyBytes is streamed to the client and never stored. Pass 0 for unbounded buffering (the pre-cap behaviour, not recommended in production).
func GetOrSet ¶
func GetOrSet(ctx context.Context, c Cache, key string, ttl time.Duration, dest any, loader Loader) error
GetOrSet returns the cached value for key, deserialized into dest. On a miss it invokes loader exactly once, even when many goroutines miss the same key concurrently, stores the result with the given TTL, and shares it with all waiters. A loader error is propagated and never cached.
dest must be a non-nil pointer. The loader's returned value is round-tripped through the cache (JSON) so the value written into dest is the cached form.
Types ¶
type Battery ¶ added in v0.3.2
type Battery struct {
// contains filtered or unexported fields
}
Battery is the framework.Battery + framework.BatteryLifecycle adapter for the cache battery. It ties the lifecycle of the underlying Cache (and especially MemoryCache's background cleanup goroutine) into the App's structured start/stop sequence.
Construct via NewBattery and register:
c := cache.NewMemoryCache(cache.WithTTL(5 * time.Minute)) app.Batteries.Register(cache.NewBattery(c))
The battery's Init is a no-op, the cache is fully configured before registration. OnStop closes the underlying cache if it implements io.Closer (MemoryCache does; RedisCache does not).
func NewBattery ¶ added in v0.3.2
NewBattery wraps c in a framework lifecycle adapter.
func (*Battery) Cache ¶ added in v0.3.2
Cache returns the underlying Cache so callers can inject it where needed without reaching through the battery wrapper.
func (*Battery) Init ¶ added in v0.3.2
Init implements framework.Battery. The cache is fully constructed by the caller before registration so Init is a no-op here.
type Cache ¶
type Cache interface {
// Get retrieves a value from the cache and deserializes it into dest.
// Returns ErrCacheMiss if the key does not exist or has expired.
Get(ctx context.Context, key string, dest any) error
// Set stores a value in the cache with the given TTL.
// A TTL of 0 means the entry uses the default TTL.
Set(ctx context.Context, key string, value any, ttl time.Duration) error
// Delete removes a key from the cache.
Delete(ctx context.Context, key string) error
// Exists checks whether a key exists in the cache and has not expired.
Exists(ctx context.Context, key string) (bool, error)
// Clear removes all entries from the cache.
Clear(ctx context.Context) error
}
Cache defines the interface for cache implementations.
type KeyScanner ¶ added in v0.78.0
KeyScanner is the optional capability a RedisClient implements when it can enumerate keys (Redis SCAN, or KEYS on a small database).
RedisCache.Clear needs it. Clear's contract is "removes all entries from the cache" — the entries THIS instance owns — and every other operation routes through prefixedKey, but Clear issued FlushDB and wiped the whole database. With the per-tenant prefixes this battery documents, one tenant's Clear was every other tenant's data-loss event, plus any foreign keys sharing the database.
With go-redis:
func (c adapter) Keys(ctx context.Context, pattern string) ([]string, error) {
var out []string
iter := c.rdb.Scan(ctx, 0, pattern, 500).Iterator()
for iter.Next(ctx) { out = append(out, iter.Val()) }
return out, iter.Err()
}
A client that does not implement it can still be used: Clear then FlushDBs an UNPREFIXED cache (which owns the database by definition) and refuses on a prefixed one rather than destroying a neighbour's keys.
type MemoryCache ¶
type MemoryCache struct {
// contains filtered or unexported fields
}
MemoryCache is an in-memory Cache implementation backed by a map with read-write mutex for thread safety. It supports TTL-based expiration with lazy eviction on access and an optional background cleanup goroutine.
func NewMemoryCache ¶
func NewMemoryCache(opts ...Option) *MemoryCache
NewMemoryCache creates a new in-memory cache. The background cleanup goroutine starts automatically when a cleanup interval is configured (default: 1 minute). Call Close to stop it.
func (*MemoryCache) Clear ¶
func (mc *MemoryCache) Clear(_ context.Context) error
Clear removes all entries from the cache.
func (*MemoryCache) Close ¶
func (mc *MemoryCache) Close()
Close stops the background cleanup goroutine.
func (*MemoryCache) Delete ¶
func (mc *MemoryCache) Delete(_ context.Context, key string) error
Delete removes a key from the cache.
func (*MemoryCache) Len ¶
func (mc *MemoryCache) Len() int
Len reports the number of live entries currently held. Primarily useful for tests and for observing eviction on a bounded cache.
type Option ¶
type Option func(*config)
Option configures a cache instance.
func WithCleanupInterval ¶
WithCleanupInterval sets the interval for the background cleanup goroutine in MemoryCache. Defaults to 1 minute if not set.
func WithMaxEntries ¶
WithMaxEntries bounds a MemoryCache to at most n live entries. Once the cap is reached, inserting a new key evicts the least-recently-used entry (LRU).
The zero-config default is UNBOUNDED (n <= 0), preserving historical behavior. Set this whenever cache keys are influenced by untrusted input to avoid unbounded memory growth (OOM/DoS). It has no effect on RedisCache, where eviction is governed by the Redis server's own maxmemory policy.
func WithPrefix ¶
WithPrefix sets a key prefix for all cache operations.
type RedisCache ¶
type RedisCache struct {
// contains filtered or unexported fields
}
RedisCache implements the Cache interface backed by a Redis store.
func NewRedisCache ¶
func NewRedisCache(client RedisClient, opts ...Option) *RedisCache
NewRedisCache creates a new Redis-backed cache.
func (*RedisCache) Clear ¶
func (rc *RedisCache) Clear(ctx context.Context) error
Clear removes the entries this cache owns.
For an UNPREFIXED cache that is the whole database, which it owns by definition, so FlushDB is correct. For a prefixed one it is only the keys prefixedKey can produce: FlushDB there destroyed every other namespace sharing the database, plus foreign keys owned by neither. Scoping needs KeyScanner; without it, a prefixed Clear refuses rather than guessing, because the wrong guess is unrecoverable.
func (*RedisCache) Delete ¶
func (rc *RedisCache) Delete(ctx context.Context, key string) error
Delete removes a key from Redis.
func (*RedisCache) Get ¶
Get retrieves a value from Redis and deserializes it into dest.
A backend failure is NOT reported as ErrCacheMiss. The Cache interface documents that sentinel strictly as "the key does not exist or has expired", and wrapping every client error in it made a connection refusal, a timeout, and an auth rejection indistinguishable from an absent key — so a caller that fails closed on a miss (negative caching, a revocation list) failed OPEN for the duration of the outage. Only the client's documented nil-signal maps to a miss; anything else is returned as itself.
type RedisClient ¶
type RedisClient interface {
// Get retrieves the string value for a key. Should return a redis nil
// error when the key does not exist.
Get(ctx context.Context, key string) (string, error)
// Set stores a string value with an optional expiration. A TTL of 0
// means no expiration.
Set(ctx context.Context, key string, value string, ttl time.Duration) error
// Del removes one or more keys.
Del(ctx context.Context, keys ...string) error
// Exists checks whether a key exists.
Exists(ctx context.Context, key string) (bool, error)
// FlushDB removes all keys from the current database.
FlushDB(ctx context.Context) error
}
RedisClient defines the minimal interface needed for a Redis cache backend. No specific Redis library is imported; implement this interface with your preferred Redis client (e.g. go-redis, redigo, etc.).