rust_web_server/cache/
mod.rs1#[cfg(test)]
33mod tests;
34
35use std::collections::{HashMap, VecDeque};
36use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
37use std::sync::{Arc, Mutex};
38use std::time::{Duration, Instant};
39
40use crate::application::Application;
41use crate::header::Header;
42use crate::middleware::Middleware;
43use crate::request::{METHOD, Request};
44use crate::response::Response;
45use crate::server::ConnectionInfo;
46
47struct CachedEntry {
50 response: Response,
51 inserted_at: Instant,
52}
53
54struct CacheStore {
55 entries: HashMap<String, CachedEntry>,
56 order: VecDeque<String>,
58}
59
60impl CacheStore {
61 fn new() -> Self {
62 CacheStore { entries: HashMap::new(), order: VecDeque::new() }
63 }
64
65 fn get(&self, key: &str, ttl: Duration) -> Option<&CachedEntry> {
66 self.entries.get(key).filter(|e| e.inserted_at.elapsed() < ttl)
67 }
68
69 fn insert(&mut self, key: String, entry: CachedEntry, capacity: usize) {
70 if self.entries.contains_key(&key) {
72 self.entries.insert(key, entry);
73 return;
74 }
75 if self.entries.len() >= capacity {
78 if let Some(oldest) = self.order.pop_front() {
79 self.entries.remove(&oldest);
80 }
81 }
82 self.order.push_back(key.clone());
83 self.entries.insert(key, entry);
84 }
85
86 fn purge_expired(&mut self, ttl: Duration) {
87 let expired: Vec<String> = self.entries.iter()
88 .filter(|(_, e)| e.inserted_at.elapsed() >= ttl)
89 .map(|(k, _)| k.clone())
90 .collect();
91 for k in &expired {
92 self.entries.remove(k);
93 self.order.retain(|o| o != k);
94 }
95 }
96}
97
98#[derive(Clone)]
109pub struct CacheLayer {
110 store: Arc<Mutex<CacheStore>>,
111 stored: Arc<AtomicUsize>,
116 hits: Arc<AtomicU64>,
117 misses: Arc<AtomicU64>,
118 capacity: usize,
119 ttl: Duration,
120 vary_headers: Vec<String>,
121}
122
123impl CacheLayer {
124 pub fn memory(capacity: usize) -> Self {
128 CacheLayer {
129 store: Arc::new(Mutex::new(CacheStore::new())),
130 stored: Arc::new(AtomicUsize::new(0)),
131 hits: Arc::new(AtomicU64::new(0)),
132 misses: Arc::new(AtomicU64::new(0)),
133 capacity,
134 ttl: Duration::from_secs(60),
135 vary_headers: vec![],
136 }
137 }
138
139 pub fn hits(&self) -> u64 {
141 self.hits.load(Ordering::Relaxed)
142 }
143
144 pub fn misses(&self) -> u64 {
146 self.misses.load(Ordering::Relaxed)
147 }
148
149 pub fn size(&self) -> usize {
152 self.store.lock().unwrap().entries.len()
153 }
154
155 pub fn clear(&self) {
157 let mut guard = self.store.lock().unwrap();
158 guard.entries.clear();
159 guard.order.clear();
160 self.stored.store(0, Ordering::Relaxed);
161 }
162
163 pub fn ttl(mut self, secs: u64) -> Self {
165 self.ttl = Duration::from_secs(secs);
166 self
167 }
168
169 pub fn vary_by_header(mut self, name: &str) -> Self {
183 self.vary_headers.push(name.to_ascii_lowercase());
184 self
185 }
186
187 fn store(&self) -> &Mutex<CacheStore> {
188 &self.store
189 }
190
191 fn cache_key(&self, request: &Request) -> String {
193 let mut key = request.request_uri.clone();
194 for vh in &self.vary_headers {
195 let val = request.headers.iter()
196 .find(|h| h.name.eq_ignore_ascii_case(vh))
197 .map(|h| h.value.as_ref())
198 .unwrap_or("");
199 key.push('\x00');
200 key.push_str(val);
201 }
202 key
203 }
204
205 fn request_bypasses_cache(request: &Request) -> bool {
208 request.headers.iter().any(|h| {
209 h.name.eq_ignore_ascii_case(Header::_CACHE_CONTROL)
210 && h.value.to_ascii_lowercase().contains("no-cache")
211 })
212 }
213
214 fn request_is_private(request: &Request) -> bool {
218 request.headers.iter().any(|h| {
219 h.name.eq_ignore_ascii_case(Header::_AUTHORIZATION)
220 || h.name.eq_ignore_ascii_case(Header::_COOKIE)
221 || h.name.eq_ignore_ascii_case(Header::_RANGE)
222 })
223 }
224
225 fn response_is_cacheable(&self, request: &Request, response: &Response) -> bool {
238 if response.status_code < 200 || response.status_code >= 300 {
239 return false;
240 }
241 if request.header(Header::_RANGE).is_some() {
242 return false;
243 }
244 if response.stream_file.is_some() || response.stream_pipe.is_some() {
247 return false;
248 }
249 if response.headers.iter().any(|h| h.name.eq_ignore_ascii_case(Header::_SET_COOKIE)) {
250 return false;
251 }
252 let has = |name: &str| response.headers.iter().any(|h| h.name.eq_ignore_ascii_case(name));
253 let varies_harmlessly = |name: &str| {
254 (name.eq_ignore_ascii_case(Header::_ORIGIN) && !has(Header::_ACCESS_CONTROL_ALLOW_ORIGIN))
255 || (name.eq_ignore_ascii_case(Header::_ACCEPT_ENCODING) && !has(Header::_CONTENT_ENCODING))
256 };
257 response.headers.iter().all(|h| {
258 if h.name.eq_ignore_ascii_case(Header::_CACHE_CONTROL) {
259 let v = h.value.to_ascii_lowercase();
260 !(v.contains("no-store") || v.contains("private") || v.contains("no-cache"))
261 } else if h.name.eq_ignore_ascii_case(Header::_VARY) {
262 h.value.split(',').map(str::trim).filter(|name| !name.is_empty()).all(|name| {
263 name != "*" && (self.vary_headers.iter().any(|vh| vh.eq_ignore_ascii_case(name)) || varies_harmlessly(name))
264 })
265 } else {
266 true
267 }
268 })
269 }
270
271 fn age_secs(entry: &CachedEntry) -> u64 {
273 entry.inserted_at.elapsed().as_secs()
274 }
275
276 fn cached_response(entry: &CachedEntry) -> Response {
278 let mut response = entry.response.clone();
279 let age = Self::age_secs(entry);
280 if let Some(h) = response.headers.iter_mut().find(|h| h.name.eq_ignore_ascii_case("Age")) {
282 h.value = age.to_string().into();
283 } else {
284 response.headers.push(Header { name: "Age".into(), value: age.to_string().into() });
285 }
286 response
287 }
288}
289
290impl Middleware for CacheLayer {
291 fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
293 true
294 }
295
296
297 fn handle(
298 &self,
299 request: &Request,
300 connection: &ConnectionInfo,
301 next: &dyn Application,
302 ) -> Result<Response, String> {
303 if request.method != METHOD.get || Self::request_is_private(request) {
305 return next.execute(request, connection);
306 }
307
308 let bypass = Self::request_bypasses_cache(request);
309
310 if !bypass && self.stored.load(Ordering::Relaxed) > 0 {
311 let key = self.cache_key(request);
313 let guard = self.store().lock().unwrap();
314 if let Some(entry) = guard.get(&key, self.ttl) {
315 self.hits.fetch_add(1, Ordering::Relaxed);
316 return Ok(Self::cached_response(entry));
317 }
318 }
319 self.misses.fetch_add(1, Ordering::Relaxed);
320
321 let response = next.execute(request, connection)?;
323
324 if self.response_is_cacheable(request, &response) {
325 let key = self.cache_key(request);
326 let mut guard = self.store().lock().unwrap();
327 guard.purge_expired(self.ttl);
328 guard.insert(
329 key,
330 CachedEntry { response: response.clone(), inserted_at: Instant::now() },
331 self.capacity,
332 );
333 self.stored.store(guard.entries.len(), Ordering::Relaxed);
334 }
335
336 Ok(response)
337 }
338
339 fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
341 true
342 }
343}