1#![deny(missing_docs)]
6
7use std::ops::Bound;
11use std::sync::Arc as StdArc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::time::{Duration, SystemTime};
14
15use headers::{
16 CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
17};
18use http::{HeaderMap, Method, StatusCode, header};
19use log::{debug, error};
20use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
21use malloc_size_of_derive::MallocSizeOf;
22use net_traits::http_status::HttpStatus;
23use net_traits::request::{CacheMode, Request};
24use net_traits::response::{Response, ResponseBody};
25use net_traits::{CacheEntryDescriptor, FetchMetadata, Metadata, ResourceFetchTiming};
26use parking_lot::Mutex as ParkingLotMutex;
27use quick_cache::sync::{Cache, PlaceholderGuard};
28use quick_cache::{DefaultHashBuilder, Lifecycle, UnitWeighter};
29use serde::{Deserialize, Serialize};
30use servo_arc::Arc;
31use servo_config::pref;
32use servo_url::ServoUrl;
33use tokio::sync::mpsc::{UnboundedSender as TokioSender, unbounded_channel as unbounded};
34use tokio::sync::{OwnedRwLockWriteGuard, RwLock as TokioRwLock};
35
36use crate::disk_cache::DiskCache;
37use crate::fetch::methods::{Data, DoneChannel};
38
39#[derive(
43 Copy,
44 Clone,
45 Default,
46 Debug,
47 Deserialize,
48 MallocSizeOf,
49 Serialize,
50 PartialEq,
51 Eq,
52 PartialOrd,
53 Ord,
54)]
55struct ApproxDuration(u64);
56
57impl ApproxDuration {
58 const fn zero() -> Self {
59 Self(0)
60 }
61
62 fn is_zero(&self) -> bool {
63 self.0 == 0
64 }
65
66 fn saturating_sub(&self, rhs: ApproxDuration) -> Self {
67 Self(self.0.saturating_sub(rhs.0))
68 }
69
70 fn from_secs(seconds: u64) -> Self {
71 Self(seconds)
72 }
73}
74
75impl From<Duration> for ApproxDuration {
76 fn from(value: Duration) -> Self {
77 Self(value.as_secs())
78 }
79}
80
81impl std::ops::Add for ApproxDuration {
82 type Output = Self;
83
84 fn add(self, rhs: Self) -> Self::Output {
85 Self(self.0 + rhs.0)
86 }
87}
88
89impl std::ops::Sub for ApproxDuration {
90 type Output = Self;
91
92 fn sub(self, rhs: Self) -> Self::Output {
93 Self(self.0 - rhs.0)
94 }
95}
96
97impl std::ops::Div<u64> for ApproxDuration {
98 type Output = Self;
99
100 fn div(self, rhs: u64) -> Self::Output {
101 Self(self.0 / rhs)
102 }
103}
104
105#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
107pub struct CacheKey {
108 url: ServoUrl,
109}
110
111impl AsRef<str> for CacheKey {
112 fn as_ref(&self) -> &str {
113 self.url.as_str()
114 }
115}
116
117impl CacheKey {
118 pub fn new(request: &Request) -> CacheKey {
120 CacheKey {
121 url: request.current_url(),
122 }
123 }
124
125 pub fn from_url(url: ServoUrl) -> CacheKey {
127 CacheKey { url }
128 }
129}
130
131#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
133pub struct CachedResource {
134 #[conditional_malloc_size_of]
135 request_headers: Arc<ParkingLotMutex<SerializeableHeaderMap>>,
136 #[conditional_malloc_size_of]
137 body: Arc<ParkingLotMutex<ResponseBody>>,
138 #[conditional_malloc_size_of]
139 aborted: Arc<AtomicBool>,
140 #[conditional_malloc_size_of]
141 #[serde(skip)]
142 awaiting_body: Arc<ParkingLotMutex<Vec<TokioSender<Data>>>>,
143 metadata: CachedMetadata,
144 location_url: Option<Result<ServoUrl, String>>,
145 status: HttpStatus,
146 url_list: Vec<ServoUrl>,
147 expires: ApproxDuration,
148 stale_while_revalidate: ApproxDuration,
149 #[conditional_malloc_size_of]
150 #[serde(skip)]
151 revalidating: StdArc<AtomicBool>,
152 last_validated: SystemTime,
153}
154
155impl CachedResource {
156 pub(crate) fn is_done(&self) -> bool {
157 self.body.lock().is_done()
158 }
159}
160
161#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
162struct SerializeableHeaderMap(
164 #[serde(
165 deserialize_with = "hyper_serde::deserialize",
166 serialize_with = "hyper_serde::serialize"
167 )]
168 HeaderMap,
169);
170
171impl std::ops::Deref for SerializeableHeaderMap {
172 type Target = HeaderMap;
173
174 fn deref(&self) -> &Self::Target {
175 &self.0
176 }
177}
178
179impl std::ops::DerefMut for SerializeableHeaderMap {
180 fn deref_mut(&mut self) -> &mut Self::Target {
181 &mut self.0
182 }
183}
184
185impl From<HeaderMap> for SerializeableHeaderMap {
186 fn from(value: HeaderMap) -> Self {
187 SerializeableHeaderMap(value)
188 }
189}
190
191#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
193struct CachedMetadata {
194 #[conditional_malloc_size_of]
196 pub headers: Arc<ParkingLotMutex<SerializeableHeaderMap>>,
197 pub final_url: ServoUrl,
199 pub content_type: Option<String>,
201 pub charset: Option<String>,
203 pub status: HttpStatus,
205}
206
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
209pub enum ValidationStatus {
210 Valid,
212 Stale {
214 revalidate_in_background: bool,
217 },
218}
219
220pub(crate) struct CachedResponse {
222 pub response: Response,
224 pub validation_status: ValidationStatus,
226 pub revalidation_guard: StdArc<AtomicBool>,
228}
229
230pub(crate) type CacheEntry = std::sync::Arc<TokioRwLock<Vec<CachedResource>>>;
231type QuickCache =
232 Cache<CacheKey, CacheEntry, UnitWeighter, DefaultHashBuilder, MemoryCacheLifecycle>;
233type QuickCachePlaceholderGuard<'a> = PlaceholderGuard<
234 'a,
235 CacheKey,
236 CacheEntry,
237 UnitWeighter,
238 DefaultHashBuilder,
239 MemoryCacheLifecycle,
240>;
241
242#[derive(Debug, MallocSizeOf, PartialEq)]
244pub enum HttpCacheAssignment {
245 Public,
247 Private,
249}
250
251pub struct HttpCache {
257 entries: QuickCache,
259 disk_cache: Option<std::sync::Arc<DiskCache>>,
260}
261
262impl MallocSizeOf for HttpCache {
263 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
264 self.entries
265 .iter()
266 .map(|(_key, entry)| entry.blocking_read().size_of(ops))
267 .sum::<usize>() +
268 self.disk_cache
269 .as_ref()
270 .map(|data| data.size_of(ops))
271 .unwrap_or(0)
272 }
273}
274
275impl HttpCache {
276 pub fn new(assignment: HttpCacheAssignment) -> Self {
278 let size = pref!(network_http_cache_size)
279 .try_into()
280 .expect("http_cache_size needs to fit into u64");
281 let (disk_cache, lifecycle) = DiskCache::new(assignment);
282 let memory_cache = Cache::with(
283 size,
284 size as u64,
285 UnitWeighter,
286 DefaultHashBuilder::default(),
287 lifecycle,
288 );
289
290 Self {
291 entries: memory_cache,
292 disk_cache,
293 }
294 }
295}
296
297#[derive(Clone)]
298pub struct MemoryCacheLifecycle {
301 pub(crate) disk_cache: Option<std::sync::Arc<DiskCache>>,
302}
303
304impl MemoryCacheLifecycle {
305 pub(crate) fn empty() -> MemoryCacheLifecycle {
306 MemoryCacheLifecycle { disk_cache: None }
307 }
308}
309
310impl Lifecycle<CacheKey, CacheEntry> for MemoryCacheLifecycle {
311 type RequestState = ();
312
313 fn on_evict(&self, _state: &mut Self::RequestState, key: CacheKey, value: CacheEntry) {
314 if let Some(disk_cache_data) = &self.disk_cache {
315 let disk_cache_data = disk_cache_data.clone();
316 tokio::spawn(async move { disk_cache_data.store(key, value).await });
317 }
318 }
319}
320
321fn is_cacheable_by_default(status_code: StatusCode) -> bool {
323 matches!(
324 status_code.as_u16(),
325 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
326 )
327}
328
329fn response_is_cacheable(metadata: &Metadata) -> bool {
332 let mut is_cacheable = false;
336 let headers = metadata.headers.as_ref().unwrap();
337 if headers.contains_key(header::EXPIRES) ||
338 headers.contains_key(header::LAST_MODIFIED) ||
339 headers.contains_key(header::ETAG)
340 {
341 is_cacheable = true;
342 }
343 if let Some(ref directive) = headers.typed_get::<CacheControl>() {
344 if directive.no_store() {
345 return false;
346 }
347 if directive.public() ||
348 directive.s_max_age().is_some() ||
349 directive.max_age().is_some() ||
350 directive.no_cache()
351 {
352 return true;
354 }
355 }
356 if let Some(pragma) = headers.typed_get::<Pragma>() &&
357 pragma.is_no_cache()
358 {
359 return false;
360 }
361 is_cacheable
362}
363
364fn calculate_response_age(response: &Response) -> ApproxDuration {
367 response
369 .headers
370 .get(header::AGE)
371 .and_then(|age_header| age_header.to_str().ok())
372 .and_then(|age_string| age_string.parse::<u64>().ok())
373 .map(ApproxDuration::from_secs)
374 .unwrap_or_default()
375}
376
377fn get_response_expiry(response: &Response) -> ApproxDuration {
380 let age = calculate_response_age(response);
382 let now = SystemTime::now();
383 if let Some(directives) = response.headers.typed_get::<CacheControl>() {
384 if directives.no_cache() {
385 return ApproxDuration::zero();
387 }
388 if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
389 let max_age: ApproxDuration = max_age.into();
390 return max_age.saturating_sub(age);
391 }
392 }
393 match response.headers.typed_get::<Expires>() {
394 Some(expiry) => {
395 let expiry_time: SystemTime = expiry.into();
398 return expiry_time
399 .duration_since(now)
400 .map(|duration| duration.into())
401 .unwrap_or(ApproxDuration::zero());
402 },
403 None if response.headers.contains_key(header::EXPIRES) => return ApproxDuration::zero(),
405 _ => {},
406 }
407 if let Some(ref code) = response.status.try_code() {
410 let max_heuristic = ApproxDuration::from_secs(24 * 60 * 60).saturating_sub(age);
414 let heuristic_freshness = if let Some(last_modified) =
415 response.headers.typed_get::<LastModified>()
419 {
420 let last_modified: SystemTime = last_modified.into();
423 let time_since_last_modified: ApproxDuration =
424 now.duration_since(last_modified).unwrap_or_default().into();
425
426 let raw_heuristic_calc = time_since_last_modified / 10;
428 if raw_heuristic_calc < max_heuristic {
429 raw_heuristic_calc
430 } else {
431 max_heuristic
432 }
433 } else {
434 ApproxDuration::zero()
436 };
437 if is_cacheable_by_default(*code) {
438 return heuristic_freshness;
440 }
441 if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
443 directives.public()
444 {
445 return heuristic_freshness;
446 }
447 }
448 ApproxDuration::zero()
450}
451
452fn get_stale_while_revalidate(headers: &HeaderMap) -> ApproxDuration {
456 for value in headers.get_all(header::CACHE_CONTROL) {
457 let Ok(value) = value.to_str() else {
458 continue;
459 };
460 for directive in value.split(',') {
461 let directive = directive.trim();
462 let Some((name, argument)) = directive.split_once('=') else {
463 continue;
464 };
465 if !name.trim().eq_ignore_ascii_case("stale-while-revalidate") {
466 continue;
467 }
468 let argument = argument.trim().trim_matches('"');
470 if let Ok(seconds) = argument.parse::<u64>() {
471 return ApproxDuration::from_secs(seconds);
472 }
473 }
474 }
475 ApproxDuration::zero()
476}
477
478fn request_demands_revalidation(request: &Request) -> bool {
481 if matches!(
482 request.cache_mode,
483 CacheMode::NoCache | CacheMode::Reload | CacheMode::NoStore
484 ) {
485 return true;
486 }
487 if let Some(directive) = request.headers.typed_get::<CacheControl>() {
488 if directive.no_cache() {
492 return true;
493 }
494
495 if directive.max_age() == Some(Duration::ZERO) {
496 return true;
497 }
498 }
499 false
500}
501
502fn get_expiry_adjustment_from_request_headers(
505 request: &Request,
506 expires: ApproxDuration,
507) -> ApproxDuration {
508 let Some(directive) = request.headers.typed_get::<CacheControl>() else {
509 return expires;
510 };
511
512 if let Some(max_age) = directive.max_stale() {
513 let max_age: ApproxDuration = max_age.into();
514 return expires + max_age;
515 };
516
517 let max_age: Option<ApproxDuration> = directive.max_age().map(|max_age| max_age.into());
518 match max_age {
519 Some(max_age) if expires > max_age => return ApproxDuration::zero(),
520 Some(max_age) => return expires - max_age,
521 None => {},
522 };
523
524 if let Some(min_fresh) = directive.min_fresh() {
525 let min_fresh: ApproxDuration = min_fresh.into();
526 if expires < min_fresh {
527 return ApproxDuration::zero();
528 };
529 return expires - min_fresh;
530 }
531
532 if directive.no_cache() || directive.no_store() {
533 return ApproxDuration::zero();
534 }
535
536 expires
537}
538
539fn create_cached_response(
541 request: &Request,
542 cached_resource: &CachedResource,
543 cached_headers: &HeaderMap,
544 done_chan: &mut DoneChannel,
545) -> Option<CachedResponse> {
546 debug!("creating a cached response for {:?}", request.url());
547 if cached_resource.aborted.load(Ordering::Acquire) {
548 return None;
549 }
550 let resource_timing = ResourceFetchTiming::new(request.timing_type());
551 let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
552 response.headers = cached_headers.clone();
553 response.body = cached_resource.body.clone();
554 if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
555 debug!("existing body is in progress");
556 let (done_sender, done_receiver) = unbounded();
557 *done_chan = Some((done_sender.clone(), done_receiver));
558 cached_resource.awaiting_body.lock().push(done_sender);
559 }
560 response
561 .location_url
562 .clone_from(&cached_resource.location_url);
563 response.status.clone_from(&cached_resource.status);
564 response.url_list.clone_from(&cached_resource.url_list);
565 response.referrer = request.referrer.to_url().cloned();
566 response.referrer_policy = request.referrer_policy;
567 response.aborted = cached_resource.aborted.clone();
568
569 let expires = cached_resource.expires;
570 let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
571 let Ok(time_since_validated) = SystemTime::now().duration_since(cached_resource.last_validated)
572 else {
573 return None;
574 };
575
576 let time_since_validated: ApproxDuration = time_since_validated.into();
577 let has_expired = adjusted_expires <= time_since_validated;
581
582 let stale_for = time_since_validated.saturating_sub(adjusted_expires);
587 let within_stale_while_revalidate_window = stale_for <= cached_resource.stale_while_revalidate;
588 let validation_status = if !has_expired {
589 ValidationStatus::Valid
590 } else {
591 ValidationStatus::Stale {
592 revalidate_in_background: within_stale_while_revalidate_window &&
593 !cached_resource.stale_while_revalidate.is_zero() &&
594 !request_demands_revalidation(request),
595 }
596 };
597
598 let cached_response = CachedResponse {
599 response,
600 validation_status,
601 revalidation_guard: cached_resource.revalidating.clone(),
602 };
603 Some(cached_response)
604}
605
606fn create_resource_with_bytes_from_resource(
609 bytes: &[u8],
610 resource: &CachedResource,
611) -> CachedResource {
612 CachedResource {
613 request_headers: resource.request_headers.clone(),
614 body: Arc::new(ParkingLotMutex::new(ResponseBody::Done(bytes.to_owned()))),
615 aborted: Arc::new(AtomicBool::new(false)),
616 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
617 metadata: resource.metadata.clone(),
618 location_url: resource.location_url.clone(),
619 status: StatusCode::PARTIAL_CONTENT.into(),
620 url_list: resource.url_list.clone(),
621 expires: resource.expires,
622 stale_while_revalidate: resource.stale_while_revalidate,
623 revalidating: resource.revalidating.clone(),
624 last_validated: resource.last_validated,
625 }
626}
627
628fn handle_range_request(
630 request: &Request,
631 candidates: &[&CachedResource],
632 range_spec: &Range,
633 done_chan: &mut DoneChannel,
634) -> Option<CachedResponse> {
635 let mut complete_cached_resources = candidates
636 .iter()
637 .filter(|resource| resource.status == StatusCode::OK);
638 let partial_cached_resources = candidates
639 .iter()
640 .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
641 if let Some(complete_resource) = complete_cached_resources.next() {
642 let body_len = match *complete_resource.body.lock() {
651 ResponseBody::Done(ref body) => body.len(),
652 _ => 0,
653 };
654 let bound = range_spec
655 .satisfiable_ranges(body_len.try_into().unwrap())
656 .next()
657 .unwrap();
658 match bound {
659 (Bound::Included(beginning), Bound::Included(end)) => {
660 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
661 if end == u64::MAX {
662 return None;
664 }
665 let b = beginning as usize;
666 let e = end as usize + 1;
667 let requested = body.get(b..e);
668 if let Some(bytes) = requested {
669 let new_resource =
670 create_resource_with_bytes_from_resource(bytes, complete_resource);
671 let cached_headers = new_resource.metadata.headers.lock();
672 let cached_response = create_cached_response(
673 request,
674 &new_resource,
675 &cached_headers,
676 done_chan,
677 );
678 if let Some(cached_response) = cached_response {
679 return Some(cached_response);
680 }
681 }
682 }
683 },
684 (Bound::Included(beginning), Bound::Unbounded) => {
685 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
686 let b = beginning as usize;
687 let requested = body.get(b..);
688 if let Some(bytes) = requested {
689 let new_resource =
690 create_resource_with_bytes_from_resource(bytes, complete_resource);
691 let cached_headers = new_resource.metadata.headers.lock();
692 let cached_response = create_cached_response(
693 request,
694 &new_resource,
695 &cached_headers,
696 done_chan,
697 );
698 if let Some(cached_response) = cached_response {
699 return Some(cached_response);
700 }
701 }
702 }
703 },
704 _ => return None,
705 }
706 } else {
707 for partial_resource in partial_cached_resources {
708 let headers = partial_resource.metadata.headers.lock();
709 let content_range = headers.typed_get::<ContentRange>();
710
711 let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
712 continue;
713 };
714 match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
715 (Bound::Included(beginning), Bound::Included(end)) => {
716 let (res_beginning, res_end) = match content_range {
717 Some(range) => {
718 if let Some(bytes_range) = range.bytes_range() {
719 bytes_range
720 } else {
721 continue;
722 }
723 },
724 _ => continue,
725 };
726 if res_beginning <= beginning && res_end >= end {
727 let resource_body = &*partial_resource.body.lock();
728 let requested = match resource_body {
729 ResponseBody::Done(body) => {
730 let b = beginning as usize - res_beginning as usize;
731 let e = end as usize - res_beginning as usize + 1;
732 body.get(b..e)
733 },
734 _ => continue,
735 };
736 if let Some(bytes) = requested {
737 let new_resource =
738 create_resource_with_bytes_from_resource(bytes, partial_resource);
739 let cached_response =
740 create_cached_response(request, &new_resource, &headers, done_chan);
741 if let Some(cached_response) = cached_response {
742 return Some(cached_response);
743 }
744 }
745 }
746 },
747
748 (Bound::Included(beginning), Bound::Unbounded) => {
749 let (res_beginning, res_end, total) = if let Some(range) = content_range {
750 match (range.bytes_range(), range.bytes_len()) {
751 (Some(bytes_range), Some(total)) => {
752 (bytes_range.0, bytes_range.1, total)
753 },
754 _ => continue,
755 }
756 } else {
757 continue;
758 };
759 if total == 0 {
760 continue;
762 };
763 if res_beginning <= beginning && res_end == total - 1 {
764 let resource_body = &*partial_resource.body.lock();
765 let requested = match resource_body {
766 ResponseBody::Done(body) => {
767 let from_byte = beginning as usize - res_beginning as usize;
768 body.get(from_byte..)
769 },
770 _ => continue,
771 };
772 if let Some(bytes) = requested {
773 if bytes.len() as u64 + beginning < total - 1 {
774 continue;
776 }
777 let new_resource =
778 create_resource_with_bytes_from_resource(bytes, partial_resource);
779 let cached_response =
780 create_cached_response(request, &new_resource, &headers, done_chan);
781 if let Some(cached_response) = cached_response {
782 return Some(cached_response);
783 }
784 }
785 }
786 },
787
788 _ => continue,
789 }
790 }
791 }
792
793 None
794}
795
796pub(crate) fn construct_response(
799 request: &Request,
800 done_chan: &mut DoneChannel,
801 cache_result: &[CachedResource],
802) -> Option<CachedResponse> {
803 if pref!(network_http_cache_disabled) {
804 return None;
805 }
806
807 debug!("trying to construct cache response for {:?}", request.url());
809 if request.method != Method::GET {
810 debug!("non-GET method, not caching");
812 return None;
813 }
814
815 let resources = cache_result
816 .iter()
817 .filter(|r| !r.aborted.load(Ordering::Relaxed));
818 let mut candidates = vec![];
819 for cached_resource in resources {
820 let mut can_be_constructed = true;
821 let cached_headers = cached_resource.metadata.headers.lock();
822 let original_request_headers = cached_resource.request_headers.lock();
823 if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
824 if vary_value.is_any() {
825 debug!("vary value is any, not caching");
826 can_be_constructed = false
827 } else {
828 for vary_val in vary_value.iter_strs() {
831 match request.headers.get(vary_val) {
832 Some(header_data) => {
833 if let Some(original_header_data) =
835 original_request_headers.get(vary_val)
836 {
837 if original_header_data != header_data {
840 debug!("headers don't match, not caching");
841 can_be_constructed = false;
842 break;
843 }
844 }
845 },
846 None => {
847 can_be_constructed = original_request_headers.get(vary_val).is_none();
851 if !can_be_constructed {
852 debug!("vary header present, not caching");
853 }
854 },
855 }
856 if !can_be_constructed {
857 break;
858 }
859 }
860 }
861 }
862 if can_be_constructed {
863 candidates.push(cached_resource);
864 }
865 }
866 if let Some(range_spec) = request.headers.typed_get::<Range>() {
868 return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
869 }
870 while let Some(cached_resource) = candidates.pop() {
871 match cached_resource.status.try_code() {
883 Some(ref code) => {
884 if *code == StatusCode::PARTIAL_CONTENT {
885 continue;
886 }
887 },
888 None => continue,
889 }
890 let cached_headers = cached_resource.metadata.headers.lock();
894 let cached_response =
895 create_cached_response(request, cached_resource, &cached_headers, done_chan);
896 if let Some(cached_response) = cached_response {
897 return Some(cached_response);
898 }
899 }
900 debug!("couldn't find an appropriate response, not caching");
901 None
903}
904
905pub fn refresh(
908 request: &Request,
909 response: Response,
910 done_chan: &mut DoneChannel,
911 cached_resources: &mut [CachedResource],
912) -> Option<Response> {
913 assert_eq!(response.status, StatusCode::NOT_MODIFIED);
914
915 let cached_resource = cached_resources.iter_mut().next()?;
916
917 let mut constructed_response = if let Some(range_spec) = request.headers.typed_get::<Range>() {
918 handle_range_request(request, &[cached_resource], &range_spec, done_chan)
919 .map(|cached_response| cached_response.response)
920 } else {
921 let in_progress_channel = match &*cached_resource.body.lock() {
926 ResponseBody::Receiving(..) => Some(unbounded()),
927 ResponseBody::Empty | ResponseBody::Done(..) => None,
928 };
929 match in_progress_channel {
930 Some((done_sender, done_receiver)) => {
931 *done_chan = Some((done_sender.clone(), done_receiver));
932 cached_resource.awaiting_body.lock().push(done_sender);
933 },
934 None => *done_chan = None,
935 }
936 let resource_timing = ResourceFetchTiming::new(request.timing_type());
940 let mut constructed_response =
941 Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
942
943 constructed_response.body = cached_resource.body.clone();
944
945 constructed_response
946 .status
947 .clone_from(&cached_resource.status);
948 constructed_response.referrer = request.referrer.to_url().cloned();
949 constructed_response.referrer_policy = request.referrer_policy;
950 constructed_response
951 .status
952 .clone_from(&cached_resource.status);
953 constructed_response
954 .url_list
955 .clone_from(&cached_resource.url_list);
956 Some(constructed_response)
957 };
958
959 if let Some(constructed_response) = constructed_response.as_mut() {
961 {
963 let mut stored_headers = cached_resource.metadata.headers.lock();
964 stored_headers.extend(response.headers);
965 constructed_response.headers = stored_headers.clone();
966 }
967 cached_resource.expires = get_response_expiry(constructed_response);
968 cached_resource.stale_while_revalidate =
969 get_stale_while_revalidate(&constructed_response.headers);
970 cached_resource.last_validated = SystemTime::now();
971 }
972
973 constructed_response
974}
975
976pub(crate) fn invalidate_cached_resources(cached_resources: &mut [CachedResource]) {
977 for cached_resource in cached_resources.iter_mut() {
978 cached_resource.expires = ApproxDuration::zero();
979 }
980}
981
982fn resolve_location_url(
983 request: &Request,
984 response: &Response,
985 header_name: header::HeaderName,
986) -> Option<ServoUrl> {
987 response
988 .headers
989 .get(header_name)
990 .and_then(|value| value.to_str().ok())
991 .and_then(|location| request.current_url().join(location).ok())
992}
993
994impl HttpCache {
995 pub(crate) async fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
999 let entry_key = CacheKey::new(request);
1000
1001 let cached_resources = match self.entries.get(&entry_key) {
1002 None => return,
1003 Some(resources) => resources,
1004 };
1005
1006 let actual_response = response.actual_response();
1007
1008 let lock = cached_resources.read().await;
1011 let relevant_cached_resources = lock.iter().filter(|resource| {
1012 if actual_response.is_network_error() {
1013 return *resource.body.lock() == ResponseBody::Empty;
1014 }
1015 resource.status == actual_response.status
1016 });
1017
1018 for cached_resource in relevant_cached_resources {
1019 let mut awaiting_consumers = cached_resource.awaiting_body.lock();
1020 if awaiting_consumers.is_empty() {
1021 continue;
1022 }
1023 let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
1024 Data::Cancelled
1029 } else {
1030 match *cached_resource.body.lock() {
1031 ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
1032 ResponseBody::Receiving(_) => {
1033 continue;
1034 },
1035 }
1036 };
1037 for done_sender in awaiting_consumers.drain(..) {
1038 let _ = done_sender.send(to_send.clone());
1039 }
1040 }
1041 }
1042
1043 pub(crate) fn cache_entry_descriptors(&self) -> Vec<CacheEntryDescriptor> {
1045 self.entries
1046 .iter()
1047 .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string()))
1048 .collect()
1049 }
1050
1051 pub(crate) fn clear(&self) {
1053 self.entries.clear();
1054 if let Some(disk_cache) = &self.disk_cache {
1055 disk_cache.clear();
1056 }
1057 }
1058
1059 pub async fn store(&self, request: &Request, response: &Response) {
1061 let guard = self.get_or_guard(CacheKey::new(request)).await;
1062 guard.insert(request, response);
1063 }
1064
1065 pub async fn construct_response(
1067 &self,
1068 request: &Request,
1069 done_chan: &mut DoneChannel,
1070 ) -> Option<Response> {
1071 let entry = self.entries.get(&CacheKey::new(request))?;
1072 let cached_resources = entry.read().await;
1073 construct_response(request, done_chan, cached_resources.as_slice())
1074 .map(|cached| cached.response)
1075 }
1076
1077 #[cfg(feature = "test-util")]
1080 pub async fn construct_response_freshness(
1081 &self,
1082 request: &Request,
1083 done_chan: &mut DoneChannel,
1084 ) -> Option<ValidationStatus> {
1085 let entry = self.entries.get(&CacheKey::new(request))?;
1086 let cached_resources = entry.read().await;
1087 construct_response(request, done_chan, cached_resources.as_slice())
1088 .map(|cached| cached.validation_status)
1089 }
1090
1091 pub(crate) async fn invalidate_related_urls(
1093 &self,
1094 request: &Request,
1095 response: &Response,
1096 skip_key: &CacheKey,
1097 ) {
1098 for header_name in &[header::LOCATION, header::CONTENT_LOCATION] {
1099 if let Some(location_url) = resolve_location_url(request, response, header_name.clone())
1100 {
1101 let location_key = CacheKey::from_url(location_url);
1102 if &location_key != skip_key {
1103 self.invalidate_entry(&location_key).await;
1104 }
1105 }
1106 }
1107 }
1108
1109 async fn invalidate_entry(&self, key: &CacheKey) {
1110 if let Some(entry) = self.entries.get(key) {
1111 let mut guarded_resources = entry.write().await;
1112 invalidate_cached_resources(guarded_resources.as_mut_slice());
1113 }
1114 }
1115
1116 #[servo_tracing::instrument(skip(self))]
1119 pub async fn get_or_guard(&self, entry_key: CacheKey) -> CachedResourcesOrGuard<'_> {
1120 let guard_or_value = self.entries.get_value_or_guard_async(&entry_key).await;
1121 if let Ok(value) = guard_or_value {
1122 return CachedResourcesOrGuard::Value(value.write_owned().await);
1123 }
1124 let guard = guard_or_value.unwrap_err();
1125
1126 if let Some(disk_cache) = &self.disk_cache {
1127 if let Some(response) = disk_cache.get(entry_key).await {
1128 if guard.insert(response.clone()).is_err() {
1129 error!(
1130 "Cache guard is invalid. This should not happen. Cache will be in inconsistent state."
1131 );
1132 }
1133 let response = response.write_owned().await;
1134 CachedResourcesOrGuard::Value(response)
1135 } else {
1136 CachedResourcesOrGuard::Guard(guard)
1137 }
1138 } else {
1139 CachedResourcesOrGuard::Guard(guard)
1140 }
1141 }
1142}
1143
1144pub enum CachedResourcesOrGuard<'a> {
1147 Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
1149 Guard(QuickCachePlaceholderGuard<'a>),
1151}
1152
1153impl<'a> CachedResourcesOrGuard<'a> {
1154 pub fn insert(self, request: &Request, response: &Response) {
1156 if pref!(network_http_cache_disabled) {
1157 return;
1158 }
1159
1160 if request.method != Method::GET {
1161 return;
1163 }
1164 if request.headers.contains_key(header::AUTHORIZATION) {
1165 return;
1172 };
1173 let metadata = match response.metadata() {
1174 Ok(FetchMetadata::Filtered {
1175 filtered: _,
1176 unsafe_: metadata,
1177 }) |
1178 Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
1179 _ => return,
1180 };
1181 if !response_is_cacheable(&metadata) {
1182 return;
1183 }
1184 let expiry = get_response_expiry(response);
1185 let stale_while_revalidate = get_stale_while_revalidate(&response.headers);
1186 let cacheable_metadata = CachedMetadata {
1187 headers: Arc::new(ParkingLotMutex::new(response.headers.clone().into())),
1188 final_url: metadata.final_url,
1189 content_type: metadata.content_type.map(|v| v.0.to_string()),
1190 charset: metadata.charset,
1191 status: metadata.status,
1192 };
1193 let entry_resource = CachedResource {
1194 request_headers: Arc::new(ParkingLotMutex::new(request.headers.clone().into())),
1195 body: response.body.clone(),
1196 aborted: response.aborted.clone(),
1197 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
1198 metadata: cacheable_metadata,
1199 location_url: response.location_url.clone(),
1200 status: response.status.clone(),
1201 url_list: response.url_list.clone(),
1202 expires: expiry,
1203 stale_while_revalidate,
1204 revalidating: StdArc::new(AtomicBool::new(false)),
1205 last_validated: SystemTime::now(),
1206 };
1207
1208 match self {
1209 CachedResourcesOrGuard::Value(mut owned_rw_lock_write_guard) => {
1210 owned_rw_lock_write_guard.push(entry_resource);
1211 },
1212 CachedResourcesOrGuard::Guard(placeholder_guard) => {
1213 if placeholder_guard
1214 .insert(std::sync::Arc::new(TokioRwLock::new(vec![entry_resource])))
1215 .is_err()
1216 {
1217 error!("Could not insert into cache");
1218 }
1219 },
1220 }
1221 }
1222
1223 pub fn try_as_mut(&mut self) -> Option<&mut Vec<CachedResource>> {
1225 match self {
1226 CachedResourcesOrGuard::Value(owned_rw_lock_write_guard) => {
1227 Some(owned_rw_lock_write_guard.as_mut())
1228 },
1229 CachedResourcesOrGuard::Guard(_) => None,
1230 }
1231 }
1232}