cache

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: May 18, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package cache implements the cache rule evaluation CloudFront performs at the edge, per RFC 7234 + the CloudFront-specific extensions documented at:

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html

The package is pure — no HTTP, no storage. The edge handler in edge.go applies these rules to live requests/responses.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConditionalHeaders

func ConditionalHeaders(cachedHeader http.Header) http.Header

ConditionalHeaders builds the If-None-Match / If-Modified-Since header pair the cache should send on a revalidation request to the origin, derived from the cached entry's validators.

func EffectiveTTL

func EffectiveTTL(respHeader http.Header, cfg DistributionConfig, now time.Time) time.Duration

EffectiveTTL implements the CloudFront precedence:

  1. `CDN-Cache-Control` (RFC 9213) — CDN-targeted directives win over the Cache-Control browsers see. CloudFront supports it.
  2. If the origin sends `Cache-Control: s-maxage=N`, use N.
  3. Otherwise if `Cache-Control: max-age=N`, use N.
  4. Otherwise if `Expires: <date>` is in the future, use that delta.
  5. Otherwise fall back to the distribution's DefaultTTL.

The result is then clamped to [MinTTL, MaxTTL].

Special cases that override the above:

  • `Cache-Control: no-store` → returns 0 (do not cache).
  • `Cache-Control: private` → returns 0 (CloudFront treats this as "shared cache must not store").

`no-cache` is **not** zero — it caches but always revalidates; that distinction belongs to NoCacheDirective, not the TTL.

func IfModifiedSinceSatisfied

func IfModifiedSinceSatisfied(reqHeader, respHeader http.Header) bool

IfModifiedSinceSatisfied reports whether the cached entry's Last-Modified is at or before the request's If-Modified-Since. When true the cache should respond 304 (the client's copy is fresh enough).

func IfNoneMatchSatisfied

func IfNoneMatchSatisfied(reqHeader, respHeader http.Header) bool

IfNoneMatchSatisfied reports whether the request's If-None-Match matches the cached entry's ETag. Per RFC 9110 §13.1.2:

  • `*` matches any existing representation
  • any listed entity-tag (weak or strong) matching the cached one returns true

When this returns true, the cache should respond 304 Not Modified instead of the full body.

func InitialAge

func InitialAge(respHeader http.Header) time.Duration

InitialAge parses the upstream's `Age:` response header. Used to pre-age a freshly-stored cache entry — the origin may have sat in an upstream cache for some time before reaching us, and RFC 9111 §5.1 says we must carry that age forward.

Returns 0 when the header is absent / malformed / negative.

func IsCacheable

func IsCacheable(respHeader http.Header, statusCode int) (bool, string)

IsCacheable decides whether the response can be put in the cache at all. Returns (false, reason) when storage is forbidden.

Per RFC 9111 §3: any status code may be cached when the response carries **explicit** freshness information (Cache-Control max-age / s-maxage / CDN-Cache-Control max-age, or an Expires header). Without explicit freshness only the "heuristically cacheable" set (RFC 9110 §15) is stored — that's the small list CloudFront defaults to.

func Key

func Key(req *http.Request, vary []string) string

Key builds the deterministic cache key for a request. The base is method+URL; adding the values of any Vary headers (lowercased, in stable order) yields the per-variant key.

Query string handling matches CloudFront's "all" forwarded mode — the full sorted query string contributes to the key. Cookie / header forwarding beyond Vary is out of scope for this PR.

func MustRevalidate

func MustRevalidate(respHeader http.Header) bool

MustRevalidate reports whether a **fresh** cached entry must still trigger origin revalidation before it can be served. RFC 9111 §5.2.2 distinguishes:

  • `no-cache` — yes, revalidate even when fresh.
  • `must-revalidate` / `proxy-revalidate` — only matters once the entry is stale (and even then it just forbids serving stale while disconnected); fresh entries are unaffected.

So this returns true iff the no-cache directive is present.

func ParseRange

func ParseRange(header string, totalSize int64) (int64, int64, bool)

ParseRange interprets a single-range "Range: bytes=START-END" header against a known content length. Returns (start, end, true) where end is inclusive (matching the Content-Range wire format).

Multi-range requests (`bytes=0-99,200-299`) and unsatisfiable ranges return ok=false — the cache should fall through to a full fetch / 416 in those cases. Suffix ranges (`bytes=-100`) and open-ended (`bytes=100-`) are supported.

func VaryDisablesCache

func VaryDisablesCache(respHeader http.Header) bool

VaryDisablesCache returns true when the origin sent `Vary: *`, which RFC 7234 says forbids any further caching.

func VaryHeaders

func VaryHeaders(respHeader http.Header) []string

VaryHeaders extracts the comma-separated list of request headers the origin's `Vary` response header pins as part of the cache key. The names are lowercased and deduplicated for stable key building.

`Vary: *` is special — it disables caching entirely; the caller should treat that as "do not cache" rather than feeding it here.

Types

type CDNStaleDirectives

type CDNStaleDirectives struct {
	StaleWhileRevalidate time.Duration
	StaleIfError         time.Duration
}

CDNStaleDirectives is the (stale-while-revalidate, stale-if-error) pair from a response. CloudFront only honours these when they come from CDN-Cache-Control — the Cache-Control header is the browser-targeted policy and intentionally has no influence on the CDN's stale-serving behaviour. RFC 9213 §3.1 endorses the same split.

Returns zero durations when neither directive is set or CDN-Cache-Control is absent.

func ReadCDNStaleDirectives

func ReadCDNStaleDirectives(respHeader http.Header) CDNStaleDirectives

ReadCDNStaleDirectives parses CDN-Cache-Control for swr / sie. It intentionally ignores Cache-Control to match CloudFront's published behaviour.

type ClientDecision

type ClientDecision struct {
	Servable   bool
	Revalidate bool
	Reason     string
}

ClientDecision is the verdict EvaluateClient returns.

func EvaluateClient

func EvaluateClient(req RequestDirectives, age, ttl time.Duration) ClientDecision

EvaluateClient runs the request-side directives against an entry.

type Control

type Control struct {
	NoStore        bool
	NoCache        bool
	Private        bool
	Public         bool
	MustRevalidate bool
	MaxAge         *int64
	SMaxAge        *int64
}

Control is the parsed shape of a Cache-Control header. Pointers distinguish "absent" from "zero".

type DistributionConfig

type DistributionConfig struct {
	MinTTL     time.Duration
	DefaultTTL time.Duration
	MaxTTL     time.Duration
}

DistributionConfig is the subset of CloudFront's CacheBehavior that the rule evaluation needs. Real DefaultCacheBehavior carries more, but TTL clamping only depends on these three values.

type RequestDirectives

type RequestDirectives struct {
	NoCache      bool   // "always revalidate this hit before serving"
	NoStore      bool   // "do not store the response"
	OnlyIfCached bool   // "serve from cache or 504"
	MaxAge       *int64 // "I'll only accept entries whose age <= MaxAge"
	MinFresh     *int64 // "I want at least MinFresh seconds of remaining freshness"
	MaxStale     *int64 // "I'll accept stale entries up to MaxStale seconds past TTL"
	HasMaxStale  bool   // distinguishes `max-stale` (no value, means infinity) from absent
}

RequestDirectives holds the subset of `Cache-Control` directives a client may put on a *request* (RFC 9111 §5.2.1).

func ParseRequestCacheControl

func ParseRequestCacheControl(reqHeader http.Header) RequestDirectives

ParseRequestCacheControl reads the request's Cache-Control header. Same parsing logic as the response side, but exposes the request-applicable directives via a separate type so callers don't confuse the two.

Jump to

Keyboard shortcuts

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