Cache Modes
When constructing a new instance of HttpCache
, you must specify a cache mode. The cache mode determines how the cache will behave in certain situations. These modes are similar to make-fetch-happen cache options. The available cache modes are:
-
Default
: This mode will inspect the HTTP cache on the way to the network. If there is a fresh response it will be used. If there is a stale response a conditional request will be created, and a normal request otherwise. It then updates the HTTP cache with the response. If the revalidation request fails (for example, on a 500 or if you're offline), the stale response will be returned. -
NoStore
: This mode will ignore the HTTP cache on the way to the network. It will always create a normal request, and will never cache the response. -
Reload
: This mode will ignore the HTTP cache on the way to the network. It will always create a normal request, and will update the HTTP cache with the response. -
NoCache
: This mode will create a conditional request if there is a response in the HTTP cache and a normal request otherwise. It then updates the HTTP cache with the response. -
ForceCache
: This mode will inspect the HTTP cache on the way to the network. If there is a cached response it will be used regardless of freshness. If there is no cached response it will create a normal request, and will update the cache with the response. -
OnlyIfCached
: This mode will inspect the HTTP cache on the way to the network. If there is a cached response it will be used regardless of freshness. If there is no cached response it will return a504 Gateway Timeout
error. -
IgnoreRules
: This mode will ignore the HTTP headers and always store a response given it was a 200 status code. It will also ignore the staleness when retrieving a response from the cache, so expiration of the cached response will need to be handled manually. If there was no cached response it will create a normal request, and will update the cache with the response.
Maximum TTL Control
When using cache modes like IgnoreRules
that bypass server cache headers, you can use the max_ttl
option to provide expiration control. This is particularly useful for preventing cached responses from persisting indefinitely.
Usage
The max_ttl
option accepts a Duration
and sets a maximum time-to-live for cached responses:
#![allow(unused)] fn main() { use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode}; use std::time::Duration; let manager = CACacheManager::new("./cache".into(), true); let options = HttpCacheOptions { max_ttl: Some(Duration::from_secs(300)), // 5 minutes maximum ..Default::default() }; let cache = HttpCache { mode: CacheMode::IgnoreRules, // Ignore server cache headers manager, options, }; }
Behavior
- Override longer durations: If the server specifies a longer cache duration (e.g.,
max-age=3600
),max_ttl
will reduce it to the specified limit - Respect shorter durations: If the server specifies a shorter duration (e.g.,
max-age=60
), the server's shorter duration will be used - Provide fallback duration: When using
IgnoreRules
mode where server headers are ignored,max_ttl
provides the cache duration
Examples
With IgnoreRules mode:
#![allow(unused)] fn main() { // Cache everything for 5 minutes, ignoring server headers let options = HttpCacheOptions { max_ttl: Some(Duration::from_secs(300)), ..Default::default() }; let cache = HttpCache { mode: CacheMode::IgnoreRules, manager, options, }; }
With Default mode:
#![allow(unused)] fn main() { // Respect server headers but limit cache duration to 1 hour maximum let options = HttpCacheOptions { max_ttl: Some(Duration::from_hours(1)), ..Default::default() }; let cache = HttpCache { mode: CacheMode::Default, manager, options, }; }
Content-Type Based Caching
You can implement selective caching based on response content types using the response_cache_mode_fn
option. This allows you to cache only certain types of content while avoiding others.
Basic Content-Type Filtering
#![allow(unused)] fn main() { use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode}; use std::sync::Arc; let manager = CACacheManager::new("./cache".into(), true); let options = HttpCacheOptions { response_cache_mode_fn: Some(Arc::new(|_request_parts, response| { // Check the Content-Type header to decide caching behavior if let Some(content_type) = response.headers.get("content-type") { match content_type.to_str().unwrap_or("") { // Cache JSON APIs aggressively (ignore no-cache headers) ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache), // Cache images with default HTTP caching rules ct if ct.starts_with("image/") => Some(CacheMode::Default), // Cache static assets aggressively ct if ct.starts_with("text/css") => Some(CacheMode::ForceCache), ct if ct.starts_with("application/javascript") => Some(CacheMode::ForceCache), // Don't cache HTML pages (often dynamic) ct if ct.starts_with("text/html") => Some(CacheMode::NoStore), // Don't cache unknown content types _ => Some(CacheMode::NoStore), } } else { // No Content-Type header - don't cache for safety Some(CacheMode::NoStore) } })), ..Default::default() }; let cache = HttpCache { mode: CacheMode::Default, // This gets overridden by response_cache_mode_fn manager, options, }; }
Advanced Content-Type Strategies
For more complex scenarios, you can combine content-type checking with other response properties:
#![allow(unused)] fn main() { use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode}; use std::sync::Arc; use std::time::Duration; let manager = CACacheManager::new("./cache".into(), true); let options = HttpCacheOptions { response_cache_mode_fn: Some(Arc::new(|request_parts, response| { // Get content type let content_type = response.headers .get("content-type") .and_then(|ct| ct.to_str().ok()) .unwrap_or(""); // Get URL path for additional context let path = request_parts.uri.path(); match content_type { // API responses ct if ct.starts_with("application/json") => { if path.starts_with("/api/") { // Cache API responses, but respect server headers Some(CacheMode::Default) } else { // Force cache non-API JSON (like config files) Some(CacheMode::ForceCache) } }, // Static assets ct if ct.starts_with("text/css") || ct.starts_with("application/javascript") => { Some(CacheMode::ForceCache) }, // Images ct if ct.starts_with("image/") => { if response.status == 200 { Some(CacheMode::ForceCache) } else { Some(CacheMode::NoStore) // Don't cache error images } }, // HTML ct if ct.starts_with("text/html") => { if path.starts_with("/static/") { Some(CacheMode::Default) // Static HTML can be cached } else { Some(CacheMode::NoStore) // Dynamic HTML shouldn't be cached } }, // Everything else _ => Some(CacheMode::NoStore), } })), // Limit cache duration to 1 hour max max_ttl: Some(Duration::from_secs(3600)), ..Default::default() }; let cache = HttpCache { mode: CacheMode::Default, manager, options, }; }
Common Content-Type Patterns
Here are some common content-type based caching strategies:
Static Assets (Aggressive Caching):
text/css
- CSS stylesheetsapplication/javascript
- JavaScript filesimage/*
- All image typesfont/*
- Web fonts
API Responses (Conditional Caching):
application/json
- JSON APIsapplication/xml
- XML APIstext/plain
- Plain text responses
Dynamic Content (No Caching):
text/html
- HTML pages (usually dynamic)application/x-www-form-urlencoded
- Form submissions
Combining with Other Options
Content-type based caching works well with other cache options:
#![allow(unused)] fn main() { use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode}; use std::sync::Arc; use std::time::Duration; let options = HttpCacheOptions { // Content-type based mode selection response_cache_mode_fn: Some(Arc::new(|_req, response| { match response.headers.get("content-type")?.to_str().ok()? { ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache), ct if ct.starts_with("image/") => Some(CacheMode::Default), _ => Some(CacheMode::NoStore), } })), // Custom cache keys for better organization cache_key: Some(Arc::new(|req| { format!("{}:{}:{}", req.method, req.uri.host().unwrap_or(""), req.uri.path()) })), // Maximum cache duration max_ttl: Some(Duration::from_secs(1800)), // 30 minutes // Add cache status headers for debugging cache_status_headers: true, ..Default::default() }; }
This approach gives you fine-grained control over what gets cached based on the actual content type returned by the server.
Complete Per-Request Customization
The HTTP cache library provides comprehensive per-request customization capabilities for cache keys, cache options, and cache modes. Here's a complete example showing all features:
#![allow(unused)] fn main() { use http_cache::{HttpCacheOptions, CACacheManager, HttpCache, CacheMode}; use std::sync::Arc; use std::time::Duration; let manager = CACacheManager::new("./cache".into(), true); let options = HttpCacheOptions { // 1. Configure cache keys when initializing (per-request cache key override) cache_key: Some(Arc::new(|req: &http::request::Parts| { // Generate different cache keys based on request properties let path = req.uri.path(); let query = req.uri.query().unwrap_or(""); match path { // API endpoints: include user context in cache key p if p.starts_with("/api/") => { if let Some(auth) = req.headers.get("authorization") { format!("api:{}:{}:{}:authenticated", req.method, path, query) } else { format!("api:{}:{}:{}:anonymous", req.method, path, query) } }, // Static assets: simple cache key p if p.starts_with("/static/") => { format!("static:{}:{}", req.method, req.uri) }, // Dynamic pages: include important headers _ => { let accept_lang = req.headers.get("accept-language") .and_then(|h| h.to_str().ok()) .unwrap_or("en"); format!("page:{}:{}:{}:{}", req.method, path, query, accept_lang) } } })), // 2. Override cache options on a per-request basis (request-based cache mode) cache_mode_fn: Some(Arc::new(|req: &http::request::Parts| { let path = req.uri.path(); // Admin endpoints: never cache if path.starts_with("/admin/") { return CacheMode::NoStore; } // Check for cache control headers from client if req.headers.contains_key("x-no-cache") { return CacheMode::NoStore; } // Development mode: bypass cache if req.headers.get("x-env").and_then(|h| h.to_str().ok()) == Some("development") { return CacheMode::Reload; } // Static assets: force cache if path.starts_with("/static/") || path.ends_with(".css") || path.ends_with(".js") { return CacheMode::ForceCache; } // Default behavior for everything else CacheMode::Default })), // 3. Additional per-response cache override (response-based cache mode) response_cache_mode_fn: Some(Arc::new(|req: &http::request::Parts, response| { // Override cache behavior based on response content and status // Never cache error responses if response.status >= 400 { return Some(CacheMode::NoStore); } // Content-type based caching if let Some(content_type) = response.headers.get("content-type") { match content_type.to_str().unwrap_or("") { // Force cache JSON APIs even with no-cache headers ct if ct.starts_with("application/json") => Some(CacheMode::ForceCache), // Don't cache HTML in development ct if ct.starts_with("text/html") => { if req.headers.get("x-env").and_then(|h| h.to_str().ok()) == Some("development") { Some(CacheMode::NoStore) } else { None // Use default behavior } }, _ => None, } } else { None } })), // Cache busting for related resources cache_bust: Some(Arc::new(|req: &http::request::Parts, _cache_key_fn, current_key| { let path = req.uri.path(); // When updating user data, bust user-specific caches if req.method == "POST" && path.starts_with("/api/users/") { if let Some(user_id) = path.strip_prefix("/api/users/").and_then(|s| s.split('/').next()) { return vec![ format!("api:GET:/api/users/{}:authenticated", user_id), format!("api:GET:/api/users/{}:anonymous", user_id), format!("api:GET:/api/users:authenticated"), ]; } } vec![] // No cache busting by default })), // Global cache duration limit max_ttl: Some(Duration::from_hours(24)), // Enable cache status headers for debugging cache_status_headers: true, ..Default::default() }; let cache = HttpCache { mode: CacheMode::Default, // Can be overridden by cache_mode_fn and response_cache_mode_fn manager, options, }; }
Key Capabilities Summary
- Custom Cache Keys: The
cache_key
function runs for every request, allowing complete customization of cache keys based on any request property - Request-Based Cache Mode Override: The
cache_mode_fn
allows overriding cache behavior based on request properties (headers, path, method, etc.) - Response-Based Cache Mode Override: The
response_cache_mode_fn
allows overriding cache behavior based on both request and response data - Cache Busting: The
cache_bust
function allows invalidating related cache entries - Global Settings: Options like
max_ttl
andcache_status_headers
provide global configuration
All of these functions are called on a per-request basis, giving you complete control over caching behavior for each individual request.