httpcache

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2025 License: MIT Imports: 10 Imported by: 2

README

httpcache

CI Security GoDoc Go Report Card License

Package httpcache provides an http.RoundTripper implementation that works as a mostly RFC 7234 compliant cache for HTTP responses.

Note: This is a maintained fork of gregjones/httpcache, which is no longer actively maintained. This fork aims to modernize the codebase while maintaining backward compatibility, fix bugs, and add new features.

Features

  • RFC 7234 Compliant - Implements HTTP caching standards
  • Multiple Backends - Memory, Disk, Redis, LevelDB, Memcache
  • Thread-Safe - Safe for concurrent use
  • Zero Dependencies - Core package uses only Go standard library
  • Easy Integration - Drop-in replacement for http.Client
  • ETag & Validation - Automatic cache revalidation
  • Stale-If-Error - Resilient caching with RFC 5861 support
  • Private Cache - Suitable for web browsers and API clients

Quick Start

package main

import (
    "fmt"
    "io"
    "net/http"
    
    "github.com/sandrolain/httpcache"
)

func main() {
    // Create a cached HTTP client
    transport := httpcache.NewMemoryCacheTransport()
    client := transport.Client()
    
    // Make requests - second request will be cached!
    resp, _ := client.Get("https://example.com")
    io.Copy(io.Discard, resp.Body)
    resp.Body.Close()
    
    // Check if response came from cache
    if resp.Header.Get(httpcache.XFromCache) == "1" {
        fmt.Println("Response was cached!")
    }
}

Installation

go get github.com/sandrolain/httpcache

Cache Backends

httpcache supports multiple storage backends. Choose the one that fits your use case:

Built-in Backends
Backend Speed Persistence Distributed Use Case
Memory ⚡⚡⚡ Fastest ❌ No ❌ No Development, testing, single-instance apps
Disk ⚡ Slow ✅ Yes ❌ No Desktop apps, CLI tools
LevelDB ⚡⚡ Fast ✅ Yes ❌ No High-performance local cache
Redis ⚡⚡ Fast ✅ Configurable ✅ Yes Microservices, distributed systems
Memcache ⚡⚡ Fast ❌ No ✅ Yes Distributed systems, App Engine
Third-Party Backends

Usage Examples

Memory Cache (Default)
transport := httpcache.NewMemoryCacheTransport()
client := transport.Client()

Best for: Testing, development, single-instance applications

Disk Cache
import "github.com/sandrolain/httpcache/diskcache"

cache := diskcache.New("/tmp/my-cache")
transport := httpcache.NewTransport(cache)
client := &http.Client{Transport: transport}

Best for: Desktop applications, CLI tools that run repeatedly

⚠️ Breaking Change: The disk cache hashing algorithm has been changed from MD5 to SHA-256 for security reasons. Existing caches created with the original fork (gregjones/httpcache) are not compatible and will need to be regenerated.

Redis Cache
import (
    "github.com/gomodule/redigo/redis"
    rediscache "github.com/sandrolain/httpcache/redis"
)

conn, _ := redis.Dial("tcp", "localhost:6379")
cache := rediscache.NewWithClient(conn)
transport := httpcache.NewTransport(cache)
client := &http.Client{Transport: transport}

Best for: Microservices, distributed systems, high availability

LevelDB Cache
import "github.com/sandrolain/httpcache/leveldbcache"

cache, _ := leveldbcache.New("/path/to/cache")
transport := httpcache.NewTransport(cache)
client := &http.Client{Transport: transport}

Best for: High-performance local caching with persistence

Custom Transport Configuration
// Use a custom underlying transport
transport := httpcache.NewTransport(cache)
transport.Transport = &http.Transport{
    MaxIdleConns:        100,
    IdleConnTimeout:     90 * time.Second,
    DisableCompression:  false,
}
transport.MarkCachedResponses = true // Add X-From-Cache header

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

Practical Examples

See the examples/ directory for complete, runnable examples:

Each example includes:

  • Complete working code
  • Detailed README
  • Use case explanations
  • Best practices

How It Works

httpcache implements RFC 7234 (HTTP Caching) by:

  1. Intercepting HTTP requests through a custom RoundTripper
  2. Checking cache for matching responses
  3. Validating freshness using Cache-Control headers
  4. Revalidating with ETag/Last-Modified when stale
  5. Updating cache with new responses
Cache Headers Supported
  • Cache-Control (max-age, no-cache, no-store, etc.)
  • ETag and If-None-Match
  • Last-Modified and If-Modified-Since
  • Expires
  • Vary
  • stale-if-error (RFC 5861)
Detecting Cache Hits
resp, _ := client.Get(url)
if resp.Header.Get(httpcache.XFromCache) == "1" {
    // Response was served from cache
}

Advanced Features

Stale-If-Error Support

Automatically serve stale cached content when the backend is unavailable:

// Server returns 500, but cached response is served instead
resp, _ := client.Get(url) // Returns cached response, not 500 error

This implements RFC 5861 for better resilience.

Vary Header Support

Correctly handles content negotiation:

req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Accept", "application/json")
resp, _ := client.Do(req)
// Cached separately from "Accept: text/html" requests
Custom Cache Implementation

Implement the Cache interface for custom backends:

type Cache interface {
    Get(key string) (responseBytes []byte, ok bool)
    Set(key string, responseBytes []byte)
    Delete(key string)
}

See examples/custom-backend for a complete example.

Limitations

  • Private cache only - Not suitable for shared proxy caching
  • No automatic eviction - MemoryCache grows unbounded (use size-limited backends)
  • GET/HEAD only - Only caches GET and HEAD requests
  • No range requests - Range requests bypass the cache

Performance

Typical performance characteristics:

Operation Memory Disk LevelDB Redis (local)
Cache Hit ~1µs ~1ms ~100µs ~1ms
Cache Miss Network latency + ~1µs overhead
Storage RAM Disk Disk (compressed) RAM/Disk

Benchmarks vary based on response size, hardware, and network conditions.

Testing

# Run all tests
go test ./...

# Run with coverage
go test -cover ./...

# Run benchmarks
go test -bench=. ./...

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Submit a pull request

Documentation

Acknowledgments

This project is a maintained fork of gregjones/httpcache, originally created by @gregjones. The original project was archived in 2023.

We're grateful for the original work and continue to maintain this project with:

  • Bug fixes and security updates
  • Modern Go practices and tooling
  • Enhanced documentation and examples
  • Backward compatibility with the original

License

MIT License

Copyright (c) 2012 Greg Jones (original)
Copyright (c) 2025 Sandro Lain (fork maintainer)

Support

Documentation

Overview

Package httpcache provides a http.RoundTripper implementation that works as a mostly RFC-compliant cache for http responses.

It is only suitable for use as a 'private' cache (i.e. for a web-browser or an API-client and not for a shared proxy).

Index

Constants

View Source
const (

	// XFromCache is the header added to responses that are returned from the cache
	XFromCache = "X-From-Cache"
)

Variables

View Source
var ErrNoDateHeader = errors.New("no Date header")

Functions

func CachedResponse

func CachedResponse(c Cache, req *http.Request) (resp *http.Response, err error)

CachedResponse returns the cached http.Response for req if present, and nil otherwise.

func Date

func Date(respHeaders http.Header) (date time.Time, err error)

Date parses and returns the value of the Date header.

func GetLogger

func GetLogger() *slog.Logger

GetLogger returns the configured logger or the default slog logger.

func SetLogger

func SetLogger(l *slog.Logger)

SetLogger sets a custom slog.Logger instance to be used by the httpcache package. If not set, the default slog logger will be used.

Types

type Cache

type Cache interface {
	// Get returns the []byte representation of a cached response and a bool
	// set to true if the value isn't empty
	Get(key string) (responseBytes []byte, ok bool)
	// Set stores the []byte representation of a response against a key
	Set(key string, responseBytes []byte)
	// Delete removes the value associated with the key
	Delete(key string)
}

A Cache interface is used by the Transport to store and retrieve responses.

type MemoryCache

type MemoryCache struct {
	// contains filtered or unexported fields
}

MemoryCache is an implemtation of Cache that stores responses in an in-memory map.

func NewMemoryCache

func NewMemoryCache() *MemoryCache

NewMemoryCache returns a new Cache that will store items in an in-memory map

func (*MemoryCache) Delete

func (c *MemoryCache) Delete(key string)

Delete removes key from the cache

func (*MemoryCache) Get

func (c *MemoryCache) Get(key string) (resp []byte, ok bool)

Get returns the []byte representation of the response and true if present, false if not

func (*MemoryCache) Set

func (c *MemoryCache) Set(key string, resp []byte)

Set saves response resp to the cache with key

type Transport

type Transport struct {
	// The RoundTripper interface actually used to make requests
	// If nil, http.DefaultTransport is used
	Transport http.RoundTripper
	Cache     Cache
	// If true, responses returned from the cache will be given an extra header, X-From-Cache
	MarkCachedResponses bool
}

Transport is an implementation of http.RoundTripper that will return values from a cache where possible (avoiding a network request) and will additionally add validators (etag/if-modified-since) to repeated requests allowing servers to return 304 / Not Modified

func NewMemoryCacheTransport

func NewMemoryCacheTransport() *Transport

NewMemoryCacheTransport returns a new Transport using the in-memory cache implementation

func NewTransport

func NewTransport(c Cache) *Transport

NewTransport returns a new Transport with the provided Cache implementation and MarkCachedResponses set to true

func (*Transport) Client

func (t *Transport) Client() *http.Client

Client returns an *http.Client that caches responses.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error)

RoundTrip takes a Request and returns a Response

If there is a fresh Response already in cache, then it will be returned without connecting to the server.

If there is a stale Response, then any validators it contains will be set on the new request to give the server a chance to respond with NotModified. If this happens, then the cached Response will be returned.

Directories

Path Synopsis
Package diskcache provides an implementation of httpcache.Cache that uses the diskv package to supplement an in-memory map with persistent storage
Package diskcache provides an implementation of httpcache.Cache that uses the diskv package to supplement an in-memory map with persistent storage
examples
basic command
custom-backend command
diskcache command
leveldb command
redis command
Package leveldbcache provides an implementation of httpcache.Cache that uses github.com/syndtr/goleveldb/leveldb
Package leveldbcache provides an implementation of httpcache.Cache that uses github.com/syndtr/goleveldb/leveldb
Package memcache provides an implementation of httpcache.Cache that uses gomemcache to store cached responses.
Package memcache provides an implementation of httpcache.Cache that uses gomemcache to store cached responses.
Package redis provides a redis interface for http caching.
Package redis provides a redis interface for http caching.

Jump to

Keyboard shortcuts

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