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-Range
headers). All requests with aRange
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.
§ | 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
WithUpstream
option. This lets you addhttpcache
to your existing HTTP client with minimal changes. See Options for details. ↩