rust_web_server/cache/
mod.rs1#[cfg(test)]
33mod tests;
34
35use std::collections::{HashMap, VecDeque};
36use std::sync::atomic::{AtomicU64, 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 hits: Arc<AtomicU64>,
112 misses: Arc<AtomicU64>,
113 capacity: usize,
114 ttl: Duration,
115 vary_headers: Vec<String>,
116}
117
118impl CacheLayer {
119 pub fn memory(capacity: usize) -> Self {
123 CacheLayer {
124 store: Arc::new(Mutex::new(CacheStore::new())),
125 hits: Arc::new(AtomicU64::new(0)),
126 misses: Arc::new(AtomicU64::new(0)),
127 capacity,
128 ttl: Duration::from_secs(60),
129 vary_headers: vec![],
130 }
131 }
132
133 pub fn hits(&self) -> u64 {
135 self.hits.load(Ordering::Relaxed)
136 }
137
138 pub fn misses(&self) -> u64 {
140 self.misses.load(Ordering::Relaxed)
141 }
142
143 pub fn size(&self) -> usize {
146 self.store.lock().unwrap().entries.len()
147 }
148
149 pub fn clear(&self) {
151 let mut guard = self.store.lock().unwrap();
152 guard.entries.clear();
153 guard.order.clear();
154 }
155
156 pub fn ttl(mut self, secs: u64) -> Self {
158 self.ttl = Duration::from_secs(secs);
159 self
160 }
161
162 pub fn vary_by_header(mut self, name: &str) -> Self {
176 self.vary_headers.push(name.to_ascii_lowercase());
177 self
178 }
179
180 fn store(&self) -> &Mutex<CacheStore> {
181 &self.store
182 }
183
184 fn cache_key(&self, request: &Request) -> String {
186 let mut key = request.request_uri.clone();
187 for vh in &self.vary_headers {
188 let val = request.headers.iter()
189 .find(|h| h.name.eq_ignore_ascii_case(vh))
190 .map(|h| h.value.as_str())
191 .unwrap_or("");
192 key.push('\x00');
193 key.push_str(val);
194 }
195 key
196 }
197
198 fn request_bypasses_cache(request: &Request) -> bool {
201 request.headers.iter().any(|h| {
202 h.name.eq_ignore_ascii_case(Header::_CACHE_CONTROL)
203 && h.value.to_ascii_lowercase().contains("no-cache")
204 })
205 }
206
207 fn request_is_private(request: &Request) -> bool {
211 request.headers.iter().any(|h| {
212 h.name.eq_ignore_ascii_case(Header::_AUTHORIZATION)
213 || h.name.eq_ignore_ascii_case(Header::_COOKIE)
214 || h.name.eq_ignore_ascii_case(Header::_RANGE)
215 })
216 }
217
218 fn response_is_cacheable(response: &Response) -> bool {
220 if response.status_code < 200 || response.status_code >= 300 {
221 return false;
222 }
223 if response.stream_file.is_some() || response.stream_pipe.is_some() {
226 return false;
227 }
228 if response.headers.iter().any(|h| h.name.eq_ignore_ascii_case(Header::_SET_COOKIE)) {
229 return false;
230 }
231 !response.headers.iter().any(|h| {
232 if !h.name.eq_ignore_ascii_case(Header::_CACHE_CONTROL) {
233 return false;
234 }
235 let v = h.value.to_ascii_lowercase();
236 v.contains("no-store") || v.contains("private")
237 })
238 }
239
240 fn age_secs(entry: &CachedEntry) -> u64 {
242 entry.inserted_at.elapsed().as_secs()
243 }
244
245 fn cached_response(entry: &CachedEntry) -> Response {
247 let mut response = entry.response.clone();
248 let age = Self::age_secs(entry);
249 if let Some(h) = response.headers.iter_mut().find(|h| h.name.eq_ignore_ascii_case("Age")) {
251 h.value = age.to_string();
252 } else {
253 response.headers.push(Header { name: "Age".to_string(), value: age.to_string() });
254 }
255 response
256 }
257}
258
259impl Middleware for CacheLayer {
260 fn handle(
261 &self,
262 request: &Request,
263 connection: &ConnectionInfo,
264 next: &dyn Application,
265 ) -> Result<Response, String> {
266 if request.method != METHOD.get || Self::request_is_private(request) {
268 return next.execute(request, connection);
269 }
270
271 let key = self.cache_key(request);
272 let bypass = Self::request_bypasses_cache(request);
273
274 if !bypass {
275 let guard = self.store().lock().unwrap();
277 if let Some(entry) = guard.get(&key, self.ttl) {
278 self.hits.fetch_add(1, Ordering::Relaxed);
279 return Ok(Self::cached_response(entry));
280 }
281 }
282 self.misses.fetch_add(1, Ordering::Relaxed);
283
284 let response = next.execute(request, connection)?;
286
287 if Self::response_is_cacheable(&response) {
288 let mut guard = self.store().lock().unwrap();
289 guard.purge_expired(self.ttl);
290 guard.insert(
291 key,
292 CachedEntry { response: response.clone(), inserted_at: Instant::now() },
293 self.capacity,
294 );
295 }
296
297 Ok(response)
298 }
299
300 fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
302 true
303 }
304}