Documentation
¶
Overview ¶
Package httpccache (http client cache) implements a caching net/http.RoundTripper that stores responses and serves them when fresh, with pluggable storage backends and HTTP cache semantics.
Index ¶
- Constants
- Variables
- func CanonicalCacheKey(req *http.Request, ignoredHeaders []string) string
- func DefaultShouldCacheRequest(req *http.Request) bool
- func IsCacheHit(resp *http.Response) bool
- func IsCacheMiss(resp *http.Response) bool
- func IsCacheRevalidated(resp *http.Response) bool
- func IsFromCache(resp *http.Response) bool
- func NewBodyLimitReader(r io.Reader, limit int64) io.Reader
- func NewClient(config *Config) *http.Client
- func NewTransport(config *Config) http.RoundTripper
- type CacheEntry
- type CacheReason
- type CacheStatus
- type Config
- type FileStorage
- func (f *FileStorage) Delete(_ context.Context, key string) error
- func (f *FileStorage) Get(_ context.Context, key string) (*CacheEntry, io.ReadCloser, error)
- func (f *FileStorage) Prune(_ context.Context) error
- func (f *FileStorage) Set(_ context.Context, key string, entry *CacheEntry, body io.Reader) error
- type MemoryStorage
- func (m *MemoryStorage) Delete(_ context.Context, key string) error
- func (m *MemoryStorage) Get(_ context.Context, key string) (*CacheEntry, io.ReadCloser, error)
- func (m *MemoryStorage) Prune(_ context.Context) error
- func (m *MemoryStorage) Set(_ context.Context, key string, entry *CacheEntry, body io.Reader) error
- type Storage
Constants ¶
const ( // DefaultCacheStatusHeader is the default response header for cache status. DefaultCacheStatusHeader = "X-Cache-Status" // DefaultCacheReasonHeader is the default response header for cache reason. DefaultCacheReasonHeader = "X-Cache-Reason" )
Variables ¶
var DefaultIgnoredCacheKeyHeaders = []string{
"X-Request-ID",
"Request-ID",
"X-Correlation-ID",
"Correlation-ID",
"X-Trade-ID",
"Trade-ID",
"Traceparent",
"Tracestate",
"B3",
"X-B3-TraceID",
"X-B3-SpanID",
"X-B3-ParentSpanID",
"X-B3-Sampled",
"X-B3-Flags",
"X-Amzn-Trace-Id",
}
DefaultIgnoredCacheKeyHeaders are per-request metadata headers ignored by the default canonical cache key to avoid unnecessary cache fragmentation.
var ErrNotFound = errors.New("httpccache: record not found")
ErrNotFound is returned by Storage.Get when the requested key does not exist in the storage backend.
var ErrReadLimitExceeded = errors.New("httpccache: read limit exceeded")
ErrReadLimitExceeded is returned by NewBodyLimitReader when a read would exceed the configured maximum body size (more bytes remain on the reader).
Functions ¶
func CanonicalCacheKey ¶
CanonicalCacheKey builds a stable cache key from a request's method, URL, URL userinfo, and headers while excluding ignored header keys.
func DefaultShouldCacheRequest ¶
DefaultShouldCacheRequest is the built-in request eligibility check used when Config.ShouldCacheRequest is nil. It returns true for GET and HEAD requests that do not carry conditional headers (If-None-Match, If-Modified-Since, etc.), Range headers, or a Cache-Control: no-store directive. Callers can wrap this function with additional logic and pass the wrapper via Config.ShouldCacheRequest:
cfg.ShouldCacheRequest = func(req *http.Request) bool {
if req.URL.Host == "internal.api" {
return false
}
return httpccache.DefaultShouldCacheRequest(req)
}
func IsCacheHit ¶
IsCacheHit reports if the response was served from cache.
func IsCacheMiss ¶
IsCacheMiss reports if the response came from upstream.
func IsCacheRevalidated ¶
IsCacheRevalidated reports if cache revalidation succeeded via 304.
func IsFromCache ¶
IsFromCache reports if the response body came from cache material.
func NewBodyLimitReader ¶
NewBodyLimitReader wraps r so that at most limit bytes are delivered. After limit bytes have been read, the next read attempts to take one more byte from r; if successful, it returns (0, ErrReadLimitExceeded) instead of EOF.
func NewClient ¶
NewClient creates a new http.Client using the cache transport.
func NewTransport ¶
func NewTransport(config *Config) http.RoundTripper
NewTransport creates a new cache round tripper.
Types ¶
type CacheEntry ¶
type CacheEntry struct {
Key string `json:"key"`
Method string `json:"method"`
URL string `json:"url"`
StoredAt time.Time `json:"stored_at"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
ResponseStatus int `json:"response_status"`
ResponseHeader http.Header `json:"response_header"`
BodySize int64 `json:"-"` // Set by the storage implementation during Get.
VaryValues map[string]string `json:"vary_values"`
}
CacheEntry is HTTP cache metadata for a stored response. Body bytes live in Storage beside JSON produced by [CacheEntry.marshalWire].
BodySize is set by the transport after Storage.Get (not part of persisted metadata) for Content-Length on synthesized responses.
ResponseHeader holds the full upstream response headers (including ETag, Last-Modified, Vary). VaryValues records the request header values that were sent when the response was cached; they are not derivable from ResponseHeader and are required for Vary matching.
type CacheReason ¶
type CacheReason string
CacheReason is the machine-readable reason attached to cache decisions.
const ( ReasonMethodNotCacheable CacheReason = "method_not_cacheable" ReasonRequestNotCacheable CacheReason = "request_not_cacheable" ReasonRequestNoStore CacheReason = "request_no_store" ReasonResponseNoStore CacheReason = "response_no_store" ReasonStatusNotCacheable CacheReason = "status_not_cacheable" ReasonNoExplicitFreshness CacheReason = "no_explicit_freshness" ReasonInvalidFreshness CacheReason = "invalid_freshness" ReasonVaryMismatch CacheReason = "vary_mismatch" ReasonVaryWildcard CacheReason = "vary_wildcard" ReasonStaleRequiresRevalidation CacheReason = "stale_requires_revalidation" ReasonAuthorizationNotAllowed CacheReason = "authorization_not_allowed" ReasonConditionalPassthrough CacheReason = "conditional_request_passthrough" ReasonRangePassthrough CacheReason = "range_request_passthrough" ReasonStorageError CacheReason = "storage_error" ReasonObjectTooLarge CacheReason = "object_too_large" ReasonStored CacheReason = "stored" ReasonCacheHit CacheReason = "cache_hit" ReasonCacheMiss CacheReason = "cache_miss" ReasonOnlyIfCachedUnsatisfied CacheReason = "only_if_cached_unsatisfied" ReasonDecodeEntryFailed CacheReason = "decode_entry_failed" ReasonUpstreamRevalidationFailed CacheReason = "upstream_revalidation_failed" )
func CacheReasonFromResponse ¶
func CacheReasonFromResponse(resp *http.Response) CacheReason
CacheReasonFromResponse returns the typed cache reason from response headers.
func (CacheReason) IsValid ¶
func (r CacheReason) IsValid() bool
IsValid reports whether the reason is one of the known CacheReason values.
type CacheStatus ¶
type CacheStatus string
CacheStatus is the machine-readable cache status value stored in response headers and decision logs.
const ( // StatusHit indicates the returned response came directly from cache. StatusHit CacheStatus = "hit" // StatusMiss indicates an upstream request was required. StatusMiss CacheStatus = "miss" // StatusRevalidated indicates cache revalidation produced a 304 and the cached // object was returned. StatusRevalidated CacheStatus = "revalidated" // StatusBypass indicates cache logic was skipped for this request. StatusBypass CacheStatus = "bypass" )
func CacheStatusFromResponse ¶
func CacheStatusFromResponse(resp *http.Response) CacheStatus
CacheStatusFromResponse returns the typed cache status from response headers.
func (CacheStatus) IsValid ¶
func (s CacheStatus) IsValid() bool
IsValid reports whether the status is one of the known CacheStatus values.
type Config ¶
type Config struct {
// BaseTransport is the wrapped upstream transport. Defaults to
// [net/http.DefaultTransport].
BaseTransport http.RoundTripper
// Storage is the cache storage backend. Defaults to [NewMemoryStorage] with
// 1024 max entries and a 7-day max age.
Storage Storage
// Logger is used for cache decision. Defaults to [slog.DiscardHandler].
Logger *slog.Logger
// LogLevel is the [slog.Level] used by the built-in decision log. Defaults
// to [slog.LevelDebug].
LogLevel *slog.Level
// LogDecisionFunc is called whenever the cache makes a hit/miss/bypass
// decision. Defaults to a structured log call at LogLevel.
LogDecisionFunc func(ctx context.Context, logger *slog.Logger, req *http.Request, decision CacheStatus, reason CacheReason, attrs ...slog.Attr)
// ShouldCacheRequest decides if a request is eligible for caching. Defaults
// to [DefaultShouldCacheRequest]. If you override this, it's still recommended
// to call [DefaultShouldCacheRequest] after your custom logic:
//
// cfg.ShouldCacheRequest = func(req *http.Request) bool {
// if req.URL.Host == "internal.api" {
// return false
// }
// return httpccache.DefaultShouldCacheRequest(req)
// }
ShouldCacheRequest func(*http.Request) bool
// CacheKeyFunc generates the storage key for cache lookups and writes.
CacheKeyFunc func(r *http.Request, ignoredHeaders []string) string
// IgnoredCacheKeyHeaders are headers that should be ignored by the default
// cache key function because they are typically per-request metadata
// (request ID, tracing IDs, etc). Defaults to [DefaultIgnoredCacheKeyHeaders].
IgnoredCacheKeyHeaders []string
// AllowAuthorizationCaching, when true, relaxes conservative authorization
// caching behavior. Defaults to false.
AllowAuthorizationCaching bool
// AllowHeuristicFreshness, when true, enables Last-Modified heuristic
// freshness when explicit freshness directives are missing. Defaults to
// false.
AllowHeuristicFreshness bool
// MaxObjectSize is the maximum response body size (bytes) that will be
// stored. Zero means unlimited. Defaults to 0 (unlimited).
MaxObjectSize int64
// DisableResponseAnnotation, when true, disables adding cache metadata
// headers (Via, CacheStatusHeader, CacheReasonHeader) to responses.
DisableResponseAnnotation bool
// ViaProduct is the token added to the Via header when annotation is
// enabled. Defaults to "httpccache".
ViaProduct string
// CacheStatusHeader is the response header name used for cache status
// metadata. Defaults to [DefaultCacheStatusHeader] ("X-Cache-Status").
CacheStatusHeader string
// CacheReasonHeader is the response header name used for cache reason
// metadata. Defaults to [DefaultCacheReasonHeader] ("X-Cache-Reason").
CacheReasonHeader string
}
Config is the configuration for the cache transport.
type FileStorage ¶
type FileStorage struct {
Dir string
MaxEntries int
MaxAge time.Duration
// contains filtered or unexported fields
}
FileStorage stores cache entries on disk as:
<metadata-json>\n<body-bytes>
Filenames are opaque and versioned.
func NewFileStorage ¶
NewFileStorage creates a new filesystem storage backend.
func (*FileStorage) Delete ¶
func (f *FileStorage) Delete(_ context.Context, key string) error
Delete removes a cached file for a key.
func (*FileStorage) Get ¶
func (f *FileStorage) Get(_ context.Context, key string) (*CacheEntry, io.ReadCloser, error)
Get retrieves a record by key from filesystem storage.
func (*FileStorage) Prune ¶
func (f *FileStorage) Prune(_ context.Context) error
Prune removes stale entries, evicts by MaxEntries, and purges mismatched storage versions.
func (*FileStorage) Set ¶
func (f *FileStorage) Set(_ context.Context, key string, entry *CacheEntry, body io.Reader) error
Set writes a record by key to filesystem storage.
type MemoryStorage ¶
type MemoryStorage struct {
MaxEntries int
MaxAge time.Duration
// contains filtered or unexported fields
}
MemoryStorage is an in-memory implementation of Storage.
func NewMemoryStorage ¶
func NewMemoryStorage(maxEntries int, maxAge time.Duration) *MemoryStorage
NewMemoryStorage creates a new in-memory storage backend.
func (*MemoryStorage) Delete ¶
func (m *MemoryStorage) Delete(_ context.Context, key string) error
Delete removes a record by key.
func (*MemoryStorage) Get ¶
func (m *MemoryStorage) Get(_ context.Context, key string) (*CacheEntry, io.ReadCloser, error)
Get retrieves a record by key from in-memory storage.
func (*MemoryStorage) Prune ¶
func (m *MemoryStorage) Prune(_ context.Context) error
Prune deletes expired entries and evicts old entries beyond max capacity.
func (*MemoryStorage) Set ¶
func (m *MemoryStorage) Set(_ context.Context, key string, entry *CacheEntry, body io.Reader) error
Set stores a record in memory and prunes old entries.
type Storage ¶
type Storage interface {
// Get retrieves a cached entry by key. The caller must close the body.
Get(ctx context.Context, key string) (entry *CacheEntry, body io.ReadCloser, err error)
// Set stores metadata and streams the body. When body is nil, only metadata
// and timestamps are updated and existing body bytes are preserved.
Set(ctx context.Context, key string, entry *CacheEntry, body io.Reader) error
Delete(ctx context.Context, key string) error
// Prune removes entries past the storage max-age (if configured) and enforces
// any max-capacity policy (e.g. evicting oldest). Backends that do not
// implement pruning return nil.
Prune(ctx context.Context) error
}
Storage is the backend contract used by the cache transport. Implementations store opaque metadata bytes and timestamps separately from the response body. Marshaling CacheEntry to metadata is the caller's responsibility.