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.
- 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, andimmutable(view 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.
Demonstration of HTTP caching in action. See _examples/app for code.
To install the package, run:
go get github.com/bartventer/httpcache
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.
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.
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.
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 entryDELETE /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().
| 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) |
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 withgregjones/httpcache. Only set for cache hits/stale/revalidated responses.
| 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 |
HTTP/1.1 200 OK X-Httpcache-Status: STALE X-From-Cache: 1 Content-Type: application/json
-
Range Requests & Partial Content: This cache does not support HTTP range requests or partial/incomplete responses (e.g., status code 206,
Range/Content-Rangeheaders). All requests with aRangeheader 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.
| § | 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 | ✔️ | Compliant by default - implementation never transforms content |
| 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 | ✔️ | Compliant by default - implementation never transforms content |
| 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 |
This project is licensed under the Apache License 2.0. See the LICENSE file for details.
Footnotes
-
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
WithUpstreamoption. This lets you addhttpcacheto your existing HTTP client with minimal changes. See Options for details. ↩