How to Cache a View Response
Problem
You want to cache HTTP GET view responses to reduce database load and speed up response times using in-memory or Redis backends.
Solution
djangors_cache::CacheLayer is a tower::Layer that caches GET responses under two strict
security requirements:
- Explicit Opt-in: the response must carry a
CacheableResponsemarker extension. - No
Set-Cookie: responses setting cookies (e.g. session cookies) are never cached, regardless of the opt-in marker.
⚠️ Current integration limitation
CacheLayer operates on the raw hyper::Response<Full<Bytes>> that comes out of the connection-
serving loop, not on djangors_core::Response. The framework’s own Response type has no
extensions_mut() (or any extensions API at all). This means there is currently no way for an
ordinary Djangors view handler to opt a response into caching. The marker has to be inserted on
the raw hyper response, which only code operating below the framework’s own dispatch layer can do.
This is the same “primitive exists, framework-level wiring doesn’t yet” gap several other Phase 7
crates (djangors-contrib-messages, -guardian, -otp) document rather than paper over. See
those guides/crates’ own docs for the same pattern.
CacheLayer is still directly usable today if you’re composing your own tower::Service stack
below djangors_core’s dispatch (i.e. you construct the CacheableResponse-marked
hyper::Response yourself, the way djangors-cache’s own test suite does):
#![allow(unused)]
fn main() {
use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use http_body_util::Full;
use hyper::{Request, Response};
use tower::{service_fn, Layer, Service, ServiceExt};
use djangors_cache::{InMemoryCache, CacheLayer, CacheableResponse};
async fn build_and_call_cached_service() {
let handler = service_fn(|_req: Request<Full<Bytes>>| async move {
let mut r = Response::new(Full::new(Bytes::from_static(b"dashboard body")));
// Opt-in: only code with direct access to the raw hyper::Response can set this today.
r.extensions_mut().insert(CacheableResponse);
Ok::<_, std::convert::Infallible>(r)
});
let cache = Arc::new(InMemoryCache::new(10_000));
let mut svc = CacheLayer::new(cache, Some(Duration::from_secs(300))).layer(handler);
let request = Request::builder()
.method("GET")
.uri("/dashboard")
.body(Full::new(Bytes::new()))
.unwrap();
let _response = svc.ready().await.unwrap().call(request).await.unwrap();
}
}
Wiring CacheableResponse into an ordinary djangors_core::Router-based view handler would
require a core djangors_core::Response change (an extensions API, or a dedicated
Response::cacheable() constructor); not attempted here since it’s out of scope for a how-to.
Track it as a real, open Phase-9-discovered gap if you need it.