Skip to main content

Module cache

Module cache 

Source
Expand description

Server-side response cache with request coalescing and stale-while-revalidate

ResponseCache keeps successful GET responses in memory so an expensive handler (database aggregate, upstream call, rendered page) runs rarely:

  • Coalescing: when many requests miss at once, one runs the handler and the others wait for its result, so a cold or expired entry cannot stampede the backend
  • Stale-while-revalidate: for a window after expiry the old response is served immediately while a single background request refreshes it, so clients never wait for a refresh
  • Safe by default: requests carrying Authorization or Cookie bypass the cache (or get their own entries with ResponseCache::per_user), responses with Set-Cookie, Cache-Control: no-store / private, streams and oversized bodies are never stored

Responses get X-Cache: HIT | MISS | STALE | COALESCED and Age. The lifetime comes from the response’s Cache-Control: max-age / s-maxage when present, otherwise from ResponseCache::ttl.

use std::time::Duration;
use ullun::cache::ResponseCache;
use ullun::prelude::*;

let cache = ResponseCache::new()
    .ttl(Duration::from_secs(30))
    .stale_while_revalidate(Duration::from_secs(120))
    .vary_by(["accept-language"]);
App::new()
    .middleware(cache.clone()) // keep a handle to invalidate after writes
    .get("/report", |_req: Request| async { Ok(Response::text("expensive")) })
    .post("/report/refresh", {
        let cache = cache.clone();
        move |_req: Request| {
            let cache = cache.clone();
            async move {
                cache.invalidate_prefix("/report");
                Ok(Response::text("ok"))
            }
        }
    })
    .run("127.0.0.1:3000")
    .await
    .unwrap();

The cache lives in the memory of one process. A request that is not cacheable (see above) is not coalesced either: each waiter then runs the handler itself.

Structs§

ResponseCache
Cache middleware, see the module documentation. Cloning gives another handle to the same cache.