Response Caching
CacheLayer is a middleware that short-circuits the inner application for cacheable GET responses, serving them directly from an in-memory store until their TTL elapses. It lives in src/cache/mod.rs and implements the standard Middleware trait, so it composes with every application variant.
Basic setup
use rust_web_server::app::App;use rust_web_server::cache::CacheLayer;use rust_web_server::core::New;
let app = App::new() .wrap(CacheLayer::memory(1000).ttl(60));This caches up to 1 000 GET responses for 60 seconds each.
Constructor and builder methods
CacheLayer::memory(capacity)
Creates a new in-memory cache bounded to capacity entries. The default TTL is 60 seconds.
let layer = CacheLayer::memory(500);.ttl(secs)
Overrides the time-to-live for all cached entries.
let layer = CacheLayer::memory(500).ttl(300); // 5 minutes.vary_by_header(name)
Includes a request header in the cache key so that different values of that header produce separate cache entries. The name match is case-insensitive. Chain multiple calls to vary by more than one header.
let layer = CacheLayer::memory(500) .vary_by_header("Accept-Language") .vary_by_header("Accept");Without .vary_by_header, all requests to the same URI share a single cache entry regardless of any request headers.
What is cached
| Condition | Cached? |
|---|---|
| Method is GET | Yes (if other conditions pass) |
| Method is POST, PUT, DELETE, etc. | No — bypasses cache entirely |
| Response status 2xx (200, 201, 203, 204, 206, …) | Yes |
| Response status 3xx, 4xx, 5xx | No |
Response Cache-Control: no-store | No |
Response Cache-Control: private | No |
Request Cache-Control: no-cache | Bypass hit, call handler, store fresh response |
Cache key
The cache key is the request URI combined with the values of every header named in .vary_by_header(). Two requests to the same path but with different Accept-Language values produce separate entries when vary_by_header("Accept-Language") is configured.
Age header
On cache hits, the response receives an Age header indicating how many seconds have elapsed since the entry was inserted:
Age: 42If the response already carries an Age header, its value is replaced.
Eviction
When the store is at capacity and a new entry must be inserted, the oldest entry (by insertion time) is evicted. Expired entries are purged before every insert, so a full store of expired entries makes room before falling back to oldest-first eviction.
Cache statistics
CacheLayer is cheaply Clone-able — all clones share the same backing store. Keep a handle to query statistics or invalidate the cache while the other clone is used as middleware:
let cache = CacheLayer::memory(1000).ttl(60);let cache_handle = cache.clone(); // shared reference
let app = App::new().wrap(cache);
// Later, from a handler or admin endpoint:let hits = cache_handle.hits(); // u64let misses = cache_handle.misses(); // u64let size = cache_handle.size(); // current entry countcache.clear()
Evicts all entries immediately:
cache_handle.clear();Full example
use rust_web_server::app::App;use rust_web_server::cache::CacheLayer;use rust_web_server::core::New;use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
let cache = CacheLayer::memory(200) .ttl(120) .vary_by_header("Accept-Language");
let cache_handle = cache.clone();
let app = App::with_state(cache_handle) .get("/api/products", |_req, _params, _conn, cache| { // This response will be cached for 120 s per Accept-Language value. let mut r = Response::new(); r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code; r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(); r }) .get("/admin/cache/stats", |_req, _params, _conn, cache| { let body = format!( "hits={} misses={} size={}", cache.hits(), cache.misses(), cache.size() ); let mut r = Response::new(); r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code; r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(); r }) .wrap(cache.clone()); // middleware layer applied outermost