1#![deny(missing_docs)]
6
7use std::ops::Bound;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::time::{Duration, Instant, SystemTime};
13
14use headers::{
15 CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
16};
17use http::{HeaderMap, Method, StatusCode, header};
18use log::{debug, error};
19use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
20use malloc_size_of_derive::MallocSizeOf;
21use net_traits::http_status::HttpStatus;
22use net_traits::request::Request;
23use net_traits::response::{Response, ResponseBody};
24use net_traits::{CacheEntryDescriptor, FetchMetadata, Metadata, ResourceFetchTiming};
25use parking_lot::Mutex as ParkingLotMutex;
26use quick_cache::sync::{Cache, DefaultLifecycle, PlaceholderGuard};
27use quick_cache::{DefaultHashBuilder, UnitWeighter};
28use servo_arc::Arc;
29use servo_config::pref;
30use servo_url::ServoUrl;
31use tokio::sync::mpsc::{UnboundedSender as TokioSender, unbounded_channel as unbounded};
32use tokio::sync::{OwnedRwLockWriteGuard, RwLock as TokioRwLock};
33
34use crate::fetch::methods::{Data, DoneChannel};
35
36#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq)]
38pub struct CacheKey {
39 url: ServoUrl,
40}
41
42impl CacheKey {
43 pub fn new(request: &Request) -> CacheKey {
45 CacheKey {
46 url: request.current_url(),
47 }
48 }
49
50 pub fn from_url(url: ServoUrl) -> CacheKey {
52 CacheKey { url }
53 }
54}
55
56#[derive(Clone, MallocSizeOf)]
58pub struct CachedResource {
59 #[conditional_malloc_size_of]
60 request_headers: Arc<ParkingLotMutex<HeaderMap>>,
61 #[conditional_malloc_size_of]
62 body: Arc<ParkingLotMutex<ResponseBody>>,
63 #[conditional_malloc_size_of]
64 aborted: Arc<AtomicBool>,
65 #[conditional_malloc_size_of]
66 awaiting_body: Arc<ParkingLotMutex<Vec<TokioSender<Data>>>>,
67 metadata: CachedMetadata,
68 location_url: Option<Result<ServoUrl, String>>,
69 status: HttpStatus,
70 url_list: Vec<ServoUrl>,
71 expires: Duration,
72 last_validated: Instant,
73}
74
75#[derive(Clone, MallocSizeOf)]
77struct CachedMetadata {
78 #[conditional_malloc_size_of]
80 pub headers: Arc<ParkingLotMutex<HeaderMap>>,
81 pub final_url: ServoUrl,
83 pub content_type: Option<String>,
85 pub charset: Option<String>,
87 pub status: HttpStatus,
89}
90pub(crate) struct CachedResponse {
92 pub response: Response,
94 pub needs_validation: bool,
96}
97
98type CacheEntry = std::sync::Arc<TokioRwLock<Vec<CachedResource>>>;
99type QuickCache = Cache<CacheKey, CacheEntry, UnitWeighter>;
100type OurLifecycle = DefaultLifecycle<CacheKey, CacheEntry>;
101type QuickCachePlaceeholderGuard<'a> =
102 PlaceholderGuard<'a, CacheKey, CacheEntry, UnitWeighter, DefaultHashBuilder, OurLifecycle>;
103
104pub struct HttpCache {
110 entries: QuickCache,
112}
113
114impl MallocSizeOf for HttpCache {
115 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
116 self.entries
117 .iter()
118 .map(|(_key, entry)| entry.blocking_read().size_of(ops))
119 .sum()
120 }
121}
122
123impl Default for HttpCache {
124 fn default() -> Self {
125 let size = pref!(network_http_cache_size)
126 .try_into()
127 .expect("http_cache_size needs to fit into u64");
128 Self {
129 entries: Cache::new(size),
130 }
131 }
132}
133
134fn is_cacheable_by_default(status_code: StatusCode) -> bool {
136 matches!(
137 status_code.as_u16(),
138 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
139 )
140}
141
142fn response_is_cacheable(metadata: &Metadata) -> bool {
145 let mut is_cacheable = false;
149 let headers = metadata.headers.as_ref().unwrap();
150 if headers.contains_key(header::EXPIRES) ||
151 headers.contains_key(header::LAST_MODIFIED) ||
152 headers.contains_key(header::ETAG)
153 {
154 is_cacheable = true;
155 }
156 if let Some(ref directive) = headers.typed_get::<CacheControl>() {
157 if directive.no_store() {
158 return false;
159 }
160 if directive.public() ||
161 directive.s_max_age().is_some() ||
162 directive.max_age().is_some() ||
163 directive.no_cache()
164 {
165 return true;
167 }
168 }
169 if let Some(pragma) = headers.typed_get::<Pragma>() &&
170 pragma.is_no_cache()
171 {
172 return false;
173 }
174 is_cacheable
175}
176
177fn calculate_response_age(response: &Response) -> Duration {
180 response
182 .headers
183 .get(header::AGE)
184 .and_then(|age_header| age_header.to_str().ok())
185 .and_then(|age_string| age_string.parse::<u64>().ok())
186 .map(Duration::from_secs)
187 .unwrap_or_default()
188}
189
190fn get_response_expiry(response: &Response) -> Duration {
193 let age = calculate_response_age(response);
195 let now = SystemTime::now();
196 if let Some(directives) = response.headers.typed_get::<CacheControl>() {
197 if directives.no_cache() {
198 return Duration::ZERO;
200 }
201 if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
202 return max_age.saturating_sub(age);
203 }
204 }
205 match response.headers.typed_get::<Expires>() {
206 Some(expiry) => {
207 let expiry_time: SystemTime = expiry.into();
210 return expiry_time.duration_since(now).unwrap_or(Duration::ZERO);
211 },
212 None if response.headers.contains_key(header::EXPIRES) => return Duration::ZERO,
214 _ => {},
215 }
216 if let Some(ref code) = response.status.try_code() {
219 let max_heuristic = Duration::from_secs(24 * 60 * 60).saturating_sub(age);
223 let heuristic_freshness = if let Some(last_modified) =
224 response.headers.typed_get::<LastModified>()
228 {
229 let last_modified: SystemTime = last_modified.into();
232 let time_since_last_modified = now.duration_since(last_modified).unwrap_or_default();
233
234 let raw_heuristic_calc = time_since_last_modified / 10;
236 if raw_heuristic_calc < max_heuristic {
237 raw_heuristic_calc
238 } else {
239 max_heuristic
240 }
241 } else {
242 max_heuristic
243 };
244 if is_cacheable_by_default(*code) {
245 return heuristic_freshness;
247 }
248 if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
250 directives.public()
251 {
252 return heuristic_freshness;
253 }
254 }
255 Duration::ZERO
257}
258
259fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Duration) -> Duration {
262 let Some(directive) = request.headers.typed_get::<CacheControl>() else {
263 return expires;
264 };
265
266 if let Some(max_age) = directive.max_stale() {
267 return expires + max_age;
268 }
269
270 match directive.max_age() {
271 Some(max_age) if expires > max_age => return Duration::ZERO,
272 Some(max_age) => return expires - max_age,
273 None => {},
274 };
275
276 if let Some(min_fresh) = directive.min_fresh() {
277 if expires < min_fresh {
278 return Duration::ZERO;
279 }
280 return expires - min_fresh;
281 }
282
283 if directive.no_cache() || directive.no_store() {
284 return Duration::ZERO;
285 }
286
287 expires
288}
289
290fn create_cached_response(
292 request: &Request,
293 cached_resource: &CachedResource,
294 cached_headers: &HeaderMap,
295 done_chan: &mut DoneChannel,
296) -> Option<CachedResponse> {
297 debug!("creating a cached response for {:?}", request.url());
298 if cached_resource.aborted.load(Ordering::Acquire) {
299 return None;
300 }
301 let resource_timing = ResourceFetchTiming::new(request.timing_type());
302 let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
303 response.headers = cached_headers.clone();
304 response.body = cached_resource.body.clone();
305 if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
306 debug!("existing body is in progress");
307 let (done_sender, done_receiver) = unbounded();
308 *done_chan = Some((done_sender.clone(), done_receiver));
309 cached_resource.awaiting_body.lock().push(done_sender);
310 }
311 response
312 .location_url
313 .clone_from(&cached_resource.location_url);
314 response.status.clone_from(&cached_resource.status);
315 response.url_list.clone_from(&cached_resource.url_list);
316 response.referrer = request.referrer.to_url().cloned();
317 response.referrer_policy = request.referrer_policy;
318 response.aborted = cached_resource.aborted.clone();
319
320 let expires = cached_resource.expires;
321 let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
322 let time_since_validated = Instant::now() - cached_resource.last_validated;
323
324 let has_expired = adjusted_expires <= time_since_validated;
328 let cached_response = CachedResponse {
329 response,
330 needs_validation: has_expired,
331 };
332 Some(cached_response)
333}
334
335fn create_resource_with_bytes_from_resource(
338 bytes: &[u8],
339 resource: &CachedResource,
340) -> CachedResource {
341 CachedResource {
342 request_headers: resource.request_headers.clone(),
343 body: Arc::new(ParkingLotMutex::new(ResponseBody::Done(bytes.to_owned()))),
344 aborted: Arc::new(AtomicBool::new(false)),
345 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
346 metadata: resource.metadata.clone(),
347 location_url: resource.location_url.clone(),
348 status: StatusCode::PARTIAL_CONTENT.into(),
349 url_list: resource.url_list.clone(),
350 expires: resource.expires,
351 last_validated: resource.last_validated,
352 }
353}
354
355fn handle_range_request(
357 request: &Request,
358 candidates: &[&CachedResource],
359 range_spec: &Range,
360 done_chan: &mut DoneChannel,
361) -> Option<CachedResponse> {
362 let mut complete_cached_resources = candidates
363 .iter()
364 .filter(|resource| resource.status == StatusCode::OK);
365 let partial_cached_resources = candidates
366 .iter()
367 .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
368 if let Some(complete_resource) = complete_cached_resources.next() {
369 let body_len = match *complete_resource.body.lock() {
378 ResponseBody::Done(ref body) => body.len(),
379 _ => 0,
380 };
381 let bound = range_spec
382 .satisfiable_ranges(body_len.try_into().unwrap())
383 .next()
384 .unwrap();
385 match bound {
386 (Bound::Included(beginning), Bound::Included(end)) => {
387 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
388 if end == u64::MAX {
389 return None;
391 }
392 let b = beginning as usize;
393 let e = end as usize + 1;
394 let requested = body.get(b..e);
395 if let Some(bytes) = requested {
396 let new_resource =
397 create_resource_with_bytes_from_resource(bytes, complete_resource);
398 let cached_headers = new_resource.metadata.headers.lock();
399 let cached_response = create_cached_response(
400 request,
401 &new_resource,
402 &cached_headers,
403 done_chan,
404 );
405 if let Some(cached_response) = cached_response {
406 return Some(cached_response);
407 }
408 }
409 }
410 },
411 (Bound::Included(beginning), Bound::Unbounded) => {
412 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
413 let b = beginning as usize;
414 let requested = body.get(b..);
415 if let Some(bytes) = requested {
416 let new_resource =
417 create_resource_with_bytes_from_resource(bytes, complete_resource);
418 let cached_headers = new_resource.metadata.headers.lock();
419 let cached_response = create_cached_response(
420 request,
421 &new_resource,
422 &cached_headers,
423 done_chan,
424 );
425 if let Some(cached_response) = cached_response {
426 return Some(cached_response);
427 }
428 }
429 }
430 },
431 _ => return None,
432 }
433 } else {
434 for partial_resource in partial_cached_resources {
435 let headers = partial_resource.metadata.headers.lock();
436 let content_range = headers.typed_get::<ContentRange>();
437
438 let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
439 continue;
440 };
441 match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
442 (Bound::Included(beginning), Bound::Included(end)) => {
443 let (res_beginning, res_end) = match content_range {
444 Some(range) => {
445 if let Some(bytes_range) = range.bytes_range() {
446 bytes_range
447 } else {
448 continue;
449 }
450 },
451 _ => continue,
452 };
453 if res_beginning <= beginning && res_end >= end {
454 let resource_body = &*partial_resource.body.lock();
455 let requested = match resource_body {
456 ResponseBody::Done(body) => {
457 let b = beginning as usize - res_beginning as usize;
458 let e = end as usize - res_beginning as usize + 1;
459 body.get(b..e)
460 },
461 _ => continue,
462 };
463 if let Some(bytes) = requested {
464 let new_resource =
465 create_resource_with_bytes_from_resource(bytes, partial_resource);
466 let cached_response =
467 create_cached_response(request, &new_resource, &headers, done_chan);
468 if let Some(cached_response) = cached_response {
469 return Some(cached_response);
470 }
471 }
472 }
473 },
474
475 (Bound::Included(beginning), Bound::Unbounded) => {
476 let (res_beginning, res_end, total) = if let Some(range) = content_range {
477 match (range.bytes_range(), range.bytes_len()) {
478 (Some(bytes_range), Some(total)) => {
479 (bytes_range.0, bytes_range.1, total)
480 },
481 _ => continue,
482 }
483 } else {
484 continue;
485 };
486 if total == 0 {
487 continue;
489 };
490 if res_beginning <= beginning && res_end == total - 1 {
491 let resource_body = &*partial_resource.body.lock();
492 let requested = match resource_body {
493 ResponseBody::Done(body) => {
494 let from_byte = beginning as usize - res_beginning as usize;
495 body.get(from_byte..)
496 },
497 _ => continue,
498 };
499 if let Some(bytes) = requested {
500 if bytes.len() as u64 + beginning < total - 1 {
501 continue;
503 }
504 let new_resource =
505 create_resource_with_bytes_from_resource(bytes, partial_resource);
506 let cached_response =
507 create_cached_response(request, &new_resource, &headers, done_chan);
508 if let Some(cached_response) = cached_response {
509 return Some(cached_response);
510 }
511 }
512 }
513 },
514
515 _ => continue,
516 }
517 }
518 }
519
520 None
521}
522
523pub(crate) fn construct_response(
526 request: &Request,
527 done_chan: &mut DoneChannel,
528 cache_result: &[CachedResource],
529) -> Option<CachedResponse> {
530 if pref!(network_http_cache_disabled) {
531 return None;
532 }
533
534 debug!("trying to construct cache response for {:?}", request.url());
536 if request.method != Method::GET {
537 debug!("non-GET method, not caching");
539 return None;
540 }
541
542 let resources = cache_result
543 .iter()
544 .filter(|r| !r.aborted.load(Ordering::Relaxed));
545 let mut candidates = vec![];
546 for cached_resource in resources {
547 let mut can_be_constructed = true;
548 let cached_headers = cached_resource.metadata.headers.lock();
549 let original_request_headers = cached_resource.request_headers.lock();
550 if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
551 if vary_value.is_any() {
552 debug!("vary value is any, not caching");
553 can_be_constructed = false
554 } else {
555 for vary_val in vary_value.iter_strs() {
558 match request.headers.get(vary_val) {
559 Some(header_data) => {
560 if let Some(original_header_data) =
562 original_request_headers.get(vary_val)
563 {
564 if original_header_data != header_data {
567 debug!("headers don't match, not caching");
568 can_be_constructed = false;
569 break;
570 }
571 }
572 },
573 None => {
574 can_be_constructed = original_request_headers.get(vary_val).is_none();
578 if !can_be_constructed {
579 debug!("vary header present, not caching");
580 }
581 },
582 }
583 if !can_be_constructed {
584 break;
585 }
586 }
587 }
588 }
589 if can_be_constructed {
590 candidates.push(cached_resource);
591 }
592 }
593 if let Some(range_spec) = request.headers.typed_get::<Range>() {
595 return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
596 }
597 while let Some(cached_resource) = candidates.pop() {
598 match cached_resource.status.try_code() {
610 Some(ref code) => {
611 if *code == StatusCode::PARTIAL_CONTENT {
612 continue;
613 }
614 },
615 None => continue,
616 }
617 let cached_headers = cached_resource.metadata.headers.lock();
621 let cached_response =
622 create_cached_response(request, cached_resource, &cached_headers, done_chan);
623 if let Some(cached_response) = cached_response {
624 return Some(cached_response);
625 }
626 }
627 debug!("couldn't find an appropriate response, not caching");
628 None
630}
631
632pub fn refresh(
635 request: &Request,
636 response: Response,
637 done_chan: &mut DoneChannel,
638 cached_resources: &mut [CachedResource],
639) -> Option<Response> {
640 assert_eq!(response.status, StatusCode::NOT_MODIFIED);
641
642 let cached_resource = cached_resources.iter_mut().next()?;
643
644 let mut constructed_response = if let Some(range_spec) = request.headers.typed_get::<Range>() {
645 handle_range_request(request, &[cached_resource], &range_spec, done_chan)
646 .map(|cached_response| cached_response.response)
647 } else {
648 let in_progress_channel = match &*cached_resource.body.lock() {
653 ResponseBody::Receiving(..) => Some(unbounded()),
654 ResponseBody::Empty | ResponseBody::Done(..) => None,
655 };
656 match in_progress_channel {
657 Some((done_sender, done_receiver)) => {
658 *done_chan = Some((done_sender.clone(), done_receiver));
659 cached_resource.awaiting_body.lock().push(done_sender);
660 },
661 None => *done_chan = None,
662 }
663 let resource_timing = ResourceFetchTiming::new(request.timing_type());
667 let mut constructed_response =
668 Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
669
670 constructed_response.body = cached_resource.body.clone();
671
672 constructed_response
673 .status
674 .clone_from(&cached_resource.status);
675 constructed_response.referrer = request.referrer.to_url().cloned();
676 constructed_response.referrer_policy = request.referrer_policy;
677 constructed_response
678 .status
679 .clone_from(&cached_resource.status);
680 constructed_response
681 .url_list
682 .clone_from(&cached_resource.url_list);
683 Some(constructed_response)
684 };
685
686 if let Some(constructed_response) = constructed_response.as_mut() {
688 cached_resource.expires = get_response_expiry(constructed_response);
689 let mut stored_headers = cached_resource.metadata.headers.lock();
690 stored_headers.extend(response.headers);
691 constructed_response.headers = stored_headers.clone();
692 }
693
694 constructed_response
695}
696
697pub(crate) fn invalidate_cached_resources(cached_resources: &mut [CachedResource]) {
698 for cached_resource in cached_resources.iter_mut() {
699 cached_resource.expires = Duration::ZERO;
700 }
701}
702
703fn resolve_location_url(
704 request: &Request,
705 response: &Response,
706 header_name: header::HeaderName,
707) -> Option<ServoUrl> {
708 response
709 .headers
710 .get(header_name)
711 .and_then(|value| value.to_str().ok())
712 .and_then(|location| request.current_url().join(location).ok())
713}
714
715impl HttpCache {
716 pub(crate) async fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
720 let entry_key = CacheKey::new(request);
721
722 let cached_resources = match self.entries.get(&entry_key) {
723 None => return,
724 Some(resources) => resources,
725 };
726
727 let actual_response = response.actual_response();
728
729 let lock = cached_resources.read().await;
732 let relevant_cached_resources = lock.iter().filter(|resource| {
733 if actual_response.is_network_error() {
734 return *resource.body.lock() == ResponseBody::Empty;
735 }
736 resource.status == actual_response.status
737 });
738
739 for cached_resource in relevant_cached_resources {
740 let mut awaiting_consumers = cached_resource.awaiting_body.lock();
741 if awaiting_consumers.is_empty() {
742 continue;
743 }
744 let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
745 Data::Cancelled
750 } else {
751 match *cached_resource.body.lock() {
752 ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
753 ResponseBody::Receiving(_) => {
754 continue;
755 },
756 }
757 };
758 for done_sender in awaiting_consumers.drain(..) {
759 let _ = done_sender.send(to_send.clone());
760 }
761 }
762 }
763
764 pub(crate) fn cache_entry_descriptors(&self) -> Vec<CacheEntryDescriptor> {
766 self.entries
767 .iter()
768 .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string()))
769 .collect()
770 }
771
772 pub(crate) fn clear(&self) {
774 self.entries.clear();
775 }
776
777 pub async fn store(&self, request: &Request, response: &Response) {
779 let guard = self.get_or_guard(CacheKey::new(request)).await;
780 guard.insert(request, response);
781 }
782
783 pub async fn construct_response(
785 &self,
786 request: &Request,
787 done_chan: &mut DoneChannel,
788 ) -> Option<Response> {
789 let entry = self.entries.get(&CacheKey::new(request))?;
790 let cached_resources = entry.read().await;
791 construct_response(request, done_chan, cached_resources.as_slice())
792 .map(|cached| cached.response)
793 }
794
795 pub(crate) async fn invalidate_related_urls(
797 &self,
798 request: &Request,
799 response: &Response,
800 skip_key: &CacheKey,
801 ) {
802 for header_name in &[header::LOCATION, header::CONTENT_LOCATION] {
803 if let Some(location_url) = resolve_location_url(request, response, header_name.clone())
804 {
805 let location_key = CacheKey::from_url(location_url);
806 if &location_key != skip_key {
807 self.invalidate_entry(&location_key).await;
808 }
809 }
810 }
811 }
812
813 async fn invalidate_entry(&self, key: &CacheKey) {
814 if let Some(entry) = self.entries.get(key) {
815 let mut guarded_resources = entry.write().await;
816 invalidate_cached_resources(guarded_resources.as_mut_slice());
817 }
818 }
819
820 pub async fn get_or_guard(&self, entry_key: CacheKey) -> CachedResourcesOrGuard<'_> {
823 match self.entries.get_value_or_guard_async(&entry_key).await {
824 Ok(val) => CachedResourcesOrGuard::Value(val.write_owned().await),
825 Err(guard) => CachedResourcesOrGuard::Guard(guard),
826 }
827 }
828}
829
830pub enum CachedResourcesOrGuard<'a> {
833 Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
835 Guard(QuickCachePlaceeholderGuard<'a>),
837}
838
839impl<'a> CachedResourcesOrGuard<'a> {
840 pub fn insert(self, request: &Request, response: &Response) {
842 if pref!(network_http_cache_disabled) {
843 return;
844 }
845
846 if request.method != Method::GET {
847 return;
849 }
850 if request.headers.contains_key(header::AUTHORIZATION) {
851 return;
858 };
859 let metadata = match response.metadata() {
860 Ok(FetchMetadata::Filtered {
861 filtered: _,
862 unsafe_: metadata,
863 }) |
864 Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
865 _ => return,
866 };
867 if !response_is_cacheable(&metadata) {
868 return;
869 }
870 let expiry = get_response_expiry(response);
871 let cacheable_metadata = CachedMetadata {
872 headers: Arc::new(ParkingLotMutex::new(response.headers.clone())),
873 final_url: metadata.final_url,
874 content_type: metadata.content_type.map(|v| v.0.to_string()),
875 charset: metadata.charset,
876 status: metadata.status,
877 };
878 let entry_resource = CachedResource {
879 request_headers: Arc::new(ParkingLotMutex::new(request.headers.clone())),
880 body: response.body.clone(),
881 aborted: response.aborted.clone(),
882 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
883 metadata: cacheable_metadata,
884 location_url: response.location_url.clone(),
885 status: response.status.clone(),
886 url_list: response.url_list.clone(),
887 expires: expiry,
888 last_validated: Instant::now(),
889 };
890
891 match self {
892 CachedResourcesOrGuard::Value(mut owned_rw_lock_write_guard) => {
893 owned_rw_lock_write_guard.push(entry_resource);
894 },
895 CachedResourcesOrGuard::Guard(placeholder_guard) => {
896 if placeholder_guard
897 .insert(std::sync::Arc::new(TokioRwLock::new(vec![entry_resource])))
898 .is_err()
899 {
900 error!("Could not insert into cache");
901 }
902 },
903 }
904 }
905
906 pub fn try_as_mut(&mut self) -> Option<&mut Vec<CachedResource>> {
908 match self {
909 CachedResourcesOrGuard::Value(owned_rw_lock_write_guard) => {
910 Some(owned_rw_lock_write_guard.as_mut())
911 },
912 CachedResourcesOrGuard::Guard(_) => None,
913 }
914 }
915}