http-cache-tower-server
Server-side HTTP response caching middleware for Tower-based frameworks (Axum, Hyper, Tonic).
Overview
This crate provides Tower middleware for caching your server's HTTP responses to improve performance and reduce load. Unlike client-side caching, this middleware caches responses after your handlers execute, making it ideal for expensive operations like database queries or complex computations.
When to Use This
Use http-cache-tower-server when you want to:
- Cache expensive API responses (database queries, aggregations)
- Reduce load on backend services
- Improve response times for read-heavy workloads
- Cache server-rendered content
- Speed up responses that are computed but rarely change
Client vs Server Caching
| Crate | Purpose | Use Case |
|---|---|---|
http-cache-tower |
Client-side caching | Cache responses from external APIs you call |
http-cache-tower-server |
Server-side caching | Cache your own application's responses |
Important: If you're experiencing issues with path parameter extraction or routing when using http-cache-tower in a server application, you should use this crate instead. See Issue #121 for details.
Installation
Features
By default, manager-cacache is enabled.
manager-cacache(default): Enable cacache disk-based cache backendmanager-moka: Enable moka in-memory cache backendmanager-foyer: Enable foyer hybrid in-memory + disk cache backend
Quick Start
Basic Example (Axum)
use ;
use ServerCacheLayer;
use CACacheManager;
use PathBuf;
async
async
How It Works
- Request arrives → Routing layer processes it (path params extracted)
- Cache lookup → Check if response is cached
- Cache hit → Return cached response immediately
- Cache miss → Call your handler
- Handler returns → Check Cache-Control headers
- Should cache? → Store response if cacheable
- Return response → Send to client
Cache Status Headers
Responses include an x-cache header indicating cache status:
x-cache: HIT→ Response served from cachex-cache: MISS→ Response generated by handler (may be cached)- No header → Response not cacheable
Cache Key Generation
Built-in Keyers
DefaultKeyer (default)
Caches based on HTTP method and path:
use ;
let layer = new;
// GET /users/123 → "GET /users/123"
// GET /users/456 → "GET /users/456"
QueryKeyer
Includes query parameters in cache key:
use ;
let layer = with_keyer;
// GET /search?q=rust → "GET /search?q=rust"
// GET /search?q=http → "GET /search?q=http"
CustomKeyer
For advanced scenarios (authentication, content negotiation, etc.):
use ;
use Request;
// Include user ID from headers in cache key
let keyer = new;
let layer = with_keyer;
// GET /dashboard with x-user-id: 123 → "GET /dashboard user:123"
// GET /dashboard with x-user-id: 456 → "GET /dashboard user:456"
Configuration Options
use ;
use Duration;
let options = ServerCacheOptions ;
let layer = new
.with_options;
Caching Behavior (RFC 9111 Compliant)
This middleware implements a shared cache per RFC 9111 (HTTP Caching).
Cached Responses
Responses are cached when they have:
- Status code: 2xx (200, 201, 204, etc.)
- Cache-Control:
max-age=X→ Cached for X seconds - Cache-Control:
s-maxage=X→ Cached for X seconds (shared cache specific) - Cache-Control:
public→ Cached with default TTL
Never Cached
Responses are never cached if they have:
- Status code: Non-2xx (4xx, 5xx, 3xx)
- Cache-Control:
no-store→ Prevents all caching - Cache-Control:
no-cache→ Requires revalidation (not supported) - Cache-Control:
private→ Only for private caches
Directive Precedence
When multiple directives are present:
s-maxage(shared cache specific) takes precedencemax-age(general directive)public(uses default TTL)- Expires header (fallback, not currently parsed)
Example Headers
// Cached for 60 seconds
// Cached for 120 seconds (s-maxage overrides max-age for shared caches)
// Cached with default TTL
// Never cached
Security Considerations
⚠️ This is a Shared Cache
Critical: Cached responses are served to ALL users. Never cache user-specific data without appropriate measures.
Safe Usage Patterns
✅ Public Content
async
✅ User-Specific with CustomKeyer
// Include user ID in cache key
let keyer = new;
❌ UNSAFE: User Data Without Keyer
// ❌ DANGEROUS: Will serve user123's data to user456!
async
✅ User Data with Private Directive
// ✅ Safe: Won't be cached
async
Best Practices
- Never cache authenticated endpoints unless using a CustomKeyer that includes session/user ID
- Use
Cache-Control: privatefor user-specific responses - Validate cache keys to prevent cache poisoning
- Set body size limits to prevent DoS attacks
- Use TTL constraints to prevent cache bloat
Advanced Examples
Content Negotiation
For responses that vary by Accept-Language:
let keyer = new;
let layer = with_keyer;
Conditional Caching
Only cache certain routes:
use middleware;
async
TTL by Route
async
async
Limitations
Vary Header
The middleware extracts Vary headers but does not currently enforce them during cache lookup. For content negotiation:
- Use a
CustomKeyerthat includes relevant headers in the cache key, OR - Set
Cache-Control: privateto prevent caching
Authorization Header
The middleware does not check for Authorization headers in requests. Authenticated endpoints should either:
- Use
Cache-Control: private(won't be cached), OR - Use a
CustomKeyerthat includes user/session ID, OR - Not be cached at all
Expires Header
The Expires header is recognized but not currently parsed. Modern applications should use Cache-Control directives instead.
Examples
See the examples directory:
axum_basic.rs- Basic usage with Axum
Run with:
Comparison with Other Crates
vs axum-response-cache
- This crate: RFC 9111 compliant, respects Cache-Control headers
- axum-response-cache: Simpler API, less RFC compliant
vs tower-cache-control
- This crate: Full caching implementation with storage
- tower-cache-control: Only sets Cache-Control headers
Minimum Supported Rust Version (MSRV)
1.88.0
Contributing
Contributions are welcome! Please see the main repository for contribution guidelines.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.