httpcache

package module
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2025 License: Apache-2.0 Imports: 12 Imported by: 1

README

httpcache

Go Reference Go Report Card Test codecov

httpcache is a Go package that provides a standards-compliant http.RoundTripper for transparent HTTP response caching, following RFC 9111 (HTTP Caching).

Note: This package is intended for use as a private (client-side) cache. It is not a shared or proxy cache. It is designed to be used with an HTTP client to cache responses from origin servers, improving performance and reducing load on those servers.

Features

  • Plug-and-Play: Just swap in as your HTTP client's transport; no extra configuration needed. [^1]
  • RFC 9111 Compliance: Handles validation, expiration, and revalidation (see details).
  • Cache Control: Supports all required HTTP cache control directives, as well as extensions like stale-while-revalidate, stale-if-error, and immutable (see details).
  • Cache Backends: Built-in support for file system and memory caches, with the ability to implement custom backends (see Cache Backends).
  • Cache Maintenance API: Optional REST endpoints for listing, retrieving, and deleting cache entries (see Cache Maintenance API).
  • Extensible: Options for logging, transport and timeouts (see Options).
  • Debuggable: Adds a cache status header to every response (see Cache Status Headers).
  • Zero Dependencies: No external dependencies, pure Go implementation.

Made with VHS

Demonstration of HTTP caching in action. See _examples/app for code.

Installation

To install the package, run:

go get github.com/bartventer/httpcache

Quick Start

To get started, create a new HTTP client with the httpcache transport, specifying a cache backend DSN. You'll need to register the desired cache backend before using it. Here's an example using the built-in file system cache:

package main

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

    "github.com/bartventer/httpcache"
    // Register the file system cache backend
    _ "github.com/bartventer/httpcache/store/fscache"
)

func main() {
    // Example DSN for the file system cache backend
    dsn := "fscache://?appname=myapp"
    client := &http.Client{
        Transport: httpcache.NewTransport(
            dsn,
            httpcache.WithSWRTimeout(10*time.Second),
            httpcache.WithLogger(slog.Default()),
        ),
    }
    // ... Use the client as usual
}

Note: The DSN format and options depend on the cache backend you choose. Refer to the Cache Backends section for details on available backends and their DSN formats.

Cache Backends

The following built-in cache backends are available:

Backend DSN Example Description
fscache fscache://?appname=myapp File system cache, stores responses on disk. Suitable for persistent caching across restarts. Supports context cancellation, as well as optional AES-GCM encryption.
memcache memcache:// In-memory cache, suitable for ephemeral caching. Does not persist across restarts.

Consult the documentation for each backend for specific configuration options and usage details.

Custom Cache Backends

To implement a custom cache backend, create a type that satisfies the store/driver.Conn interface, then register it using the store.Register function. Refer to the built-in backends for examples of how to implement this interface.

Cache Maintenance API (Debug Only)

A REST API is available for cache inspection and maintenance, intended for debugging and development use only. Do not expose these endpoints in production.

Endpoints:

  • GET /debug/httpcache — List cache keys (if supported)
  • GET /debug/httpcache/{key} — Retrieve a cache entry
  • DELETE /debug/httpcache/{key} — Delete a cache entry

All endpoints require a dsn query parameter to select the cache backend.

Usage Example:

import (
    "net/http"
    "github.com/bartventer/httpcache/store/expapi"
)

func main() {
    expapi.Register()
    http.ListenAndServe(":8080", nil)
}

To use a custom ServeMux, pass expapi.WithServeMux(mux) to expapi.Register().

Options

Option Description Default Value
WithUpstream(http.RoundTripper) Set a custom transport for upstream/origin requests http.DefaultTransport
WithSWRTimeout(time.Duration) Set the stale-while-revalidate timeout 5 * time.Second
WithLogger(*slog.Logger) Set a logger for debug output slog.New(slog.DiscardHandler)

Cache Status Headers

This package sets a cache status header on every response:

  • X-Httpcache-Status: The primary, detailed cache status header (always set).
  • X-From-Cache: (Legacy) Provided for compatibility with gregjones/httpcache. Only set for cache hits/stale/revalidated responses.
Header Value Mapping
X-Httpcache-Status X-From-Cache Description
HIT 1 Served from cache
STALE 1 Served from cache but stale
REVALIDATED 1 Revalidated with origin
MISS (not set) Served from origin
BYPASS (not set) Bypassed cache, served from origin
Example: Stale cache hit
HTTP/1.1 200 OK
X-Httpcache-Status: STALE
X-From-Cache: 1
Content-Type: application/json

Limitations

  • Range Requests & Partial Content: This cache does not support HTTP range requests or partial/incomplete responses (e.g., status code 206, Range/Content-Range headers). All requests with a Range header are bypassed, and 206 responses are not cached. For example:

    GET /example.txt HTTP/1.1
    Host: example.com
    Range: bytes=0-99
    

    The above request will bypass the cache and fetch the response directly from the origin server. See RFC 9111 §3.3-3.4 for details.

RFC 9111 Compliance Matrix

RFC 9111

§ Title Requirement Implemented Notes
1. Introduction N/A N/A Nothing to implement
2. Overview of Cache Operation N/A N/A Nothing to implement
3. Storing Responses in Caches Required ✔️ Details
4. Constructing Responses from Caches Required ✔️ Details
5. Field Definitions Required ✔️ Details
6. Relationship to Applications and Other Caches N/A N/A Nothing to implement
7. Security Considerations N/A N/A Nothing to implement
8. IANA Considerations N/A N/A Nothing to implement
9. References N/A N/A Nothing to implement

Legend for Requirements:

Requirement Description
Required Must be implemented for RFC compliance
Optional May be implemented, but not required for compliance
Obsolete Directive is no longer relevant as per RFC 9111
Deprecated Directive is deprecated as per RFC 9111, but can still be implemented
N/A Nothing to implement or not applicable to private caches
§3. Storing Responses in Caches (Details)
§ Title Requirement Implemented Notes
3.1. Storing Header and Trailer Fields Required ✔️
3.2. Updating Stored Header Fields Required ✔️
3.3. Storing Incomplete Responses Optional See Limitations
3.4. Combining Partial Content Optional See Limitations
3.5. Storing Responses to Authenticated Requests N/A N/A Not applicable to private client-side caches
§4. Constructing Responses from Caches (Details)
§ Title Requirement Implemented Notes
4.1. Calculating Cache Keys with the Vary Header Field Required ✔️
4.2. Freshness Required ✔️ Details
§4.2. Freshness (Subsections)
§ Title Requirement Implemented Notes
4.2.1. Calculating Freshness Lifetime Required ✔️
4.2.2. Calculating Heuristic Freshness Required ✔️
4.2.3. Calculating Age Required ✔️
4.2.4. Serving Stale Responses Required ✔️
§ Title Requirement Implemented Notes
4.3. Validation Required ✔️ Details
§4.3. Validation (Subsections)
§ Title Requirement Implemented Notes
4.3.1. Sending a Validation Request Required ✔️
4.3.2. Handling Received Validation Request N/A N/A Not applicable to private client-side caches
4.3.3. Handling a Validation Response Required ✔️
4.3.4. Freshening Stored Responses upon Validation Required ✔️
4.3.5. Freshening Responses with HEAD Optional Pointless, rather use conditional GETs; see RFC 9110 §13.2.1 last para
§ Title Requirement Implemented Notes
4.4. Invalidating Stored Responses Required ✔️
§5. Field Definitions (Details)
§ Title Requirement Implemented Notes
5.1. Age Required ✔️
5.2. Cache-Control Required ✔️ Details
5.3. Expires Required ✔️
5.4. Pragma Deprecated Deprecated by RFC 9111; not implemented
5.5. Warning Obsolete Obsoleted by RFC 9111; not implemented
§5.2. Cache-Control Directives
§ Title Requirement Implemented Notes
5.2.1. Request Directives Optional ✔️ Details
§5.2.1. Request Directives (Details)
§ Title/Directive Requirement Implemented Notes
5.2.1.1. max-age Optional ✔️
5.2.1.2. max-stale Optional ✔️
5.2.1.3. min-fresh Optional ✔️
5.2.1.4. no-cache Optional ✔️
5.2.1.5. no-store Optional ✔️
5.2.1.6. no-transform Optional ✔️ See Content Transformation Compliance
5.2.1.7. only-if-cached Optional ✔️
Section Requirement Implemented Notes
5.2.2. Response Directives Required ✔️ Details
§5.2.2. Response Directives (Details)
§ Title/Directive Requirement Implemented Notes
5.2.2.1. max-age Required ✔️
5.2.2.2. must-revalidate Required ✔️
5.2.2.3. must-understand Required ✔️
5.2.2.4. no-cache Required ✔️ Both qualified and unqualified forms supported
5.2.2.5. no-store Required ✔️
5.2.2.6. no-transform Required ✔️ See Content Transformation Compliance
5.2.2.7. private N/A N/A Intended for shared caches; not applicable to private caches
5.2.2.8. proxy-revalidate N/A N/A Intended for shared caches; not applicable to private caches
5.2.2.9. public Optional ✔️
5.2.2.10. s-maxage N/A N/A Intended for shared caches; not applicable to private caches
§ Title Requirement Implemented Notes
5.2.3. Extension Directives Optional partially Details
§5.2.3. Extension Directives (Details)

The following additional cache control directives are supported, as defined in various RFCs:

Reference Directive Notes
RFC 5861, §3 stale-while-revalidate Only applies to responses
RFC 5861, §4 stale-if-error Applies to both requests and responses
RFC 8246, §2 immutable Only applies to responses
Content Transformation Compliance

This implementation maintains RFC 9111 compliance regarding the no-transform directive by enforcing a storage-only architecture:

type Conn interface {
    Get(key string) ([]byte, error)    // Returns byte-identical data
    Set(key string, data []byte) error // Stores byte-identical data  
    Delete(key string) error           // Simple deletion
}

Storage optimizations (compression, encryption, serialization) are allowed because they return byte-identical HTTP responses. This differs from transformations mentioned in RFC 9110 §7.7, like image format conversion which change actual response content.

Custom backends: Must return byte-identical responses. Use the store/acceptance test suite to verify compliance.

License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.

Notes

[^1]: No configuration is needed beyond the cache backend DSN. Caching is handled automatically based on HTTP headers and directives. To use a custom upstream transport, pass it with the WithUpstream option. This lets you add httpcache to your existing HTTP client with minimal changes. See Options for details.

Documentation

Overview

Package httpcache provides an implementation of http.RoundTripper that adds transparent HTTP response caching according to RFC 9111 (HTTP Caching).

The main entry point is NewTransport, which returns an http.RoundTripper for use with http.Client. httpcache supports the required standard HTTP caching directives, as well as extension directives such as stale-while-revalidate, stale-if-error and immutable.

Example usage:

package main

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

	"github.com/bartventer/httpcache"

	// Register a cache backend by importing the package
	_ "github.com/bartventer/httpcache/store/fscache"
)

func main() {
	dsn := "fscache://?appname=myapp" // Example DSN for the file system cache backend
	client := &http.Client{
		Transport: httpcache.NewTransport(
			dsn,
			httpcache.WithSWRTimeout(10*time.Second),
			httpcache.WithLogger(slog.Default()),
		),
	}
}

Index

Constants

View Source
const (
	DefaultSWRTimeout = 5 * time.Second

	CacheStatusHeader = internal.CacheStatusHeader
)

Variables

View Source
var ErrOpenCache = errors.New("httpcache: failed to open cache")

ErrOpenCache is used as the panic value when the cache cannot be opened. You may recover from this panic if you wish to handle the situation gracefully.

Example usage:

defer func() {
	if r := recover(); r != nil {
		if err, ok := r.(error); ok && errors.Is(err, ErrOpenCache) {
			// Handle the error gracefully, e.g., log it or return a default transport
			log.Println("Failed to open cache:", err)
			client := &http.Client{
				Transport: http.DefaultTransport, // Fallback to default transport
			}
			// Use the fallback client as needed
			_ = client
		} else {
			// Re-panic if it's not the expected error
			panic(r)
		}
	}
}()

Functions

func ContextWithTraceID added in v0.10.0

func ContextWithTraceID(ctx context.Context, traceID string) context.Context

ContextWithTraceID adds a trace ID to the context, which can be used for logging or tracing purposes. The trace ID can be retrieved later using TraceIDFromContext.

func NewClient added in v0.9.0

func NewClient(dsn string, options ...Option) *http.Client

NewClient returns a new http.Client, configured with a transport that caches HTTP responses, using the specified cache backend DSN.

See NewTransport for details on the DSN and available options.

func NewTransport

func NewTransport(dsn string, options ...Option) http.RoundTripper

NewTransport returns an http.RoundTripper that caches HTTP responses using the specified cache backend.

The dsn parameter follows the format documented in store.Open. Configuration is done via functional options like WithUpstream and WithSWRTimeout.

Panics with ErrOpenCache if the cache backend cannot be opened.

func TraceIDFromContext added in v0.10.0

func TraceIDFromContext(ctx context.Context) (string, bool)

TraceIDFromContext retrieves the trace ID from the context, if it exists.

Types

type Option

type Option interface {
	// contains filtered or unexported methods
}

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets the logger for debug output; default: slog.New(slog.DiscardHandler).

func WithSWRTimeout

func WithSWRTimeout(timeout time.Duration) Option

WithSWRTimeout sets the timeout for Stale-While-Revalidate requests; default: DefaultSWRTimeout.

func WithUpstream added in v0.7.0

func WithUpstream(upstream http.RoundTripper) Option

WithUpstream sets the underlying http.RoundTripper used for upstream/origin requests. Default: http.DefaultTransport.

Note: Headers added by the upstream roundtripper (e.g., authentication headers) do not affect cache key calculation or Vary header matching (RFC 9111 §4.1). The cache operates on the original client request, not the mutated request seen by the upstream roundtripper.

Directories

Path Synopsis
_examples
app command
Package internal provides the core unexported functionality for the httpcache package.
Package internal provides the core unexported functionality for the httpcache package.
testutil
Package testutil provides utility functions for testing in Go.
Package testutil provides utility functions for testing in Go.
Package store provides a registry and entry point for cache backends.
Package store provides a registry and entry point for cache backends.
acceptance
Package acceptance provides a suite of acceptance tests for Cache implementations.
Package acceptance provides a suite of acceptance tests for Cache implementations.
driver
Package driver defines interfaces to be implemented by cache backends as used by the github.com/bartventer/httpcache/store package.
Package driver defines interfaces to be implemented by cache backends as used by the github.com/bartventer/httpcache/store package.
expapi
Package expapi provides HTTP handlers for managing and interacting with cache backends.
Package expapi provides HTTP handlers for managing and interacting with cache backends.
fscache
Package fscache implements a file system-based cache backend.
Package fscache implements a file system-based cache backend.
internal/registry
Package registry provides a registry for cache drivers.
Package registry provides a registry for cache drivers.
memcache
Package memcache implements an in-memory cache backend.
Package memcache implements an in-memory cache backend.

Jump to

Keyboard shortcuts

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