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, ContentType, Expires, HeaderMapExt, LastModified, Pragma, Range,
17 Vary,
18};
19use http::{HeaderMap, Method, StatusCode, header};
20use hyper_serde::Serde;
21use log::{debug, error, info};
22use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
23use malloc_size_of_derive::MallocSizeOf;
24use net_traits::http_status::HttpStatus;
25use net_traits::request::{CacheMode, Request};
26use net_traits::response::{Response, ResponseBody};
27use net_traits::{CacheEntryDescriptor, Metadata, ResourceFetchTiming};
28use parking_lot::Mutex as ParkingLotMutex;
29use quick_cache::sync::{Cache, PlaceholderGuard};
30use quick_cache::{DefaultHashBuilder, Lifecycle, UnitWeighter};
31use serde::{Deserialize, Serialize};
32use servo_arc::Arc;
33use servo_config::pref;
34use servo_url::ServoUrl;
35use tokio::sync::mpsc::{UnboundedSender as TokioSender, unbounded_channel as unbounded};
36use tokio::sync::{OwnedRwLockWriteGuard, RwLock as TokioRwLock};
37
38use crate::disk_cache::DiskCache;
39use crate::fetch::methods::{Data, DoneChannel};
40
41#[derive(
45 Copy,
46 Clone,
47 Default,
48 Debug,
49 Deserialize,
50 MallocSizeOf,
51 Serialize,
52 PartialEq,
53 Eq,
54 PartialOrd,
55 Ord,
56)]
57struct ApproxDuration(u64);
58
59impl ApproxDuration {
60 const fn zero() -> Self {
61 Self(0)
62 }
63
64 fn is_zero(&self) -> bool {
65 self.0 == 0
66 }
67
68 fn saturating_sub(&self, rhs: ApproxDuration) -> Self {
69 Self(self.0.saturating_sub(rhs.0))
70 }
71
72 fn from_secs(seconds: u64) -> Self {
73 Self(seconds)
74 }
75}
76
77impl From<Duration> for ApproxDuration {
78 fn from(value: Duration) -> Self {
79 Self(value.as_secs())
80 }
81}
82
83impl std::ops::Add for ApproxDuration {
84 type Output = Self;
85
86 fn add(self, rhs: Self) -> Self::Output {
87 Self(self.0 + rhs.0)
88 }
89}
90
91impl std::ops::Sub for ApproxDuration {
92 type Output = Self;
93
94 fn sub(self, rhs: Self) -> Self::Output {
95 Self(self.0 - rhs.0)
96 }
97}
98
99impl std::ops::Div<u64> for ApproxDuration {
100 type Output = Self;
101
102 fn div(self, rhs: u64) -> Self::Output {
103 Self(self.0 / rhs)
104 }
105}
106
107#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
109pub struct CacheKey {
110 url: ServoUrl,
111}
112
113impl AsRef<str> for CacheKey {
114 fn as_ref(&self) -> &str {
115 self.url.as_str()
116 }
117}
118
119impl CacheKey {
120 pub fn new(request: &Request) -> CacheKey {
122 CacheKey {
123 url: request.current_url(),
124 }
125 }
126
127 pub fn from_url(url: ServoUrl) -> CacheKey {
129 CacheKey { url }
130 }
131}
132
133#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
135pub struct CachedResource {
136 #[conditional_malloc_size_of]
137 request_headers: Arc<ParkingLotMutex<SerializeableHeaderMap>>,
138 #[conditional_malloc_size_of]
139 body: Arc<ParkingLotMutex<ResponseBody>>,
140 #[conditional_malloc_size_of]
141 aborted: Arc<AtomicBool>,
142 #[conditional_malloc_size_of]
143 #[serde(skip)]
144 awaiting_body: Arc<ParkingLotMutex<Vec<TokioSender<Data>>>>,
145 metadata: CachedMetadata,
146 location_url: Option<Result<ServoUrl, String>>,
147 status: HttpStatus,
148 url_list: Vec<ServoUrl>,
149 expires: ApproxDuration,
150 stale_while_revalidate: ApproxDuration,
151 #[conditional_malloc_size_of]
152 #[serde(skip)]
153 revalidating: StdArc<AtomicBool>,
154 last_validated: SystemTime,
155}
156
157impl CachedResource {
158 pub(crate) fn is_done(&self) -> bool {
159 self.body.lock().is_done()
160 }
161
162 fn can_be_used_to_answer(&self, request: &Request) -> bool {
170 if self.aborted.load(Ordering::Acquire) {
171 return false;
172 }
173 if !self.vary_header_matches(&request.headers) {
174 return false;
175 }
176 if request.headers.typed_get::<Range>().is_some() {
177 return true;
178 }
179
180 self.status
192 .try_code()
193 .is_some_and(|code| code != StatusCode::PARTIAL_CONTENT)
194 }
195
196 fn vary_header_matches(&self, request_headers: &HeaderMap) -> bool {
207 let cached_headers = self.metadata.headers.lock();
208 let original_request_headers = self.request_headers.lock();
209 let Some(vary_value) = cached_headers.typed_get::<Vary>() else {
210 return true;
211 };
212
213 if vary_value.is_any() {
214 return false;
215 }
216
217 for vary_header_field in vary_value.iter_strs() {
218 if request_headers.get(vary_header_field) !=
219 original_request_headers.get(vary_header_field)
220 {
221 return false;
222 }
223 }
224
225 true
226 }
227}
228
229#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
230struct SerializeableHeaderMap(
232 #[serde(
233 deserialize_with = "hyper_serde::deserialize",
234 serialize_with = "hyper_serde::serialize"
235 )]
236 HeaderMap,
237);
238
239impl std::ops::Deref for SerializeableHeaderMap {
240 type Target = HeaderMap;
241
242 fn deref(&self) -> &Self::Target {
243 &self.0
244 }
245}
246
247impl std::ops::DerefMut for SerializeableHeaderMap {
248 fn deref_mut(&mut self) -> &mut Self::Target {
249 &mut self.0
250 }
251}
252
253impl From<HeaderMap> for SerializeableHeaderMap {
254 fn from(value: HeaderMap) -> Self {
255 SerializeableHeaderMap(value)
256 }
257}
258
259#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
261struct CachedMetadata {
262 #[conditional_malloc_size_of]
264 pub headers: Arc<ParkingLotMutex<SerializeableHeaderMap>>,
265 pub final_url: ServoUrl,
267 pub content_type: Option<Serde<ContentType>>,
269 pub charset: Option<String>,
271 pub status: HttpStatus,
273}
274
275#[derive(Clone, Copy, Debug, Eq, PartialEq)]
277pub enum ValidationStatus {
278 Valid,
280 Stale {
282 revalidate_in_background: bool,
285 },
286}
287
288pub(crate) struct CachedResponse {
290 pub response: Response,
292 pub validation_status: ValidationStatus,
294 pub revalidation_guard: StdArc<AtomicBool>,
296}
297
298pub(crate) type CacheEntry = std::sync::Arc<TokioRwLock<Vec<CachedResource>>>;
299type QuickCache =
300 Cache<CacheKey, CacheEntry, UnitWeighter, DefaultHashBuilder, MemoryCacheLifecycle>;
301type QuickCachePlaceholderGuard<'a> = PlaceholderGuard<
302 'a,
303 CacheKey,
304 CacheEntry,
305 UnitWeighter,
306 DefaultHashBuilder,
307 MemoryCacheLifecycle,
308>;
309
310#[derive(Debug, MallocSizeOf, PartialEq)]
312pub enum HttpCacheAssignment {
313 Public,
315 Private,
317}
318
319pub struct HttpCache {
325 entries: QuickCache,
327 disk_cache: Option<std::sync::Arc<DiskCache>>,
328}
329
330impl std::fmt::Debug for HttpCache {
331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 f.debug_list().entries(self.entries.iter()).finish()
333 }
334}
335
336impl MallocSizeOf for HttpCache {
337 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
338 self.entries
339 .iter()
340 .map(|(_key, entry)| entry.blocking_read().size_of(ops))
341 .sum::<usize>() +
342 self.disk_cache
343 .as_ref()
344 .map(|data| data.size_of(ops))
345 .unwrap_or(0)
346 }
347}
348
349impl HttpCache {
350 pub fn new(assignment: HttpCacheAssignment) -> Self {
352 let size = pref!(network_http_cache_size)
353 .try_into()
354 .expect("http_cache_size needs to fit into u64");
355 let (disk_cache, lifecycle) = DiskCache::new(assignment);
356 let memory_cache = Cache::with(
357 size,
358 size as u64,
359 UnitWeighter,
360 DefaultHashBuilder::default(),
361 lifecycle,
362 );
363
364 Self {
365 entries: memory_cache,
366 disk_cache,
367 }
368 }
369
370 #[expect(clippy::len_without_is_empty)]
371 pub fn len(&self) -> usize {
373 self.entries.len()
374 }
375}
376
377#[derive(Clone)]
378pub struct MemoryCacheLifecycle {
381 pub(crate) disk_cache: Option<StdArc<DiskCache>>,
382}
383
384impl MemoryCacheLifecycle {
385 pub(crate) fn empty() -> MemoryCacheLifecycle {
386 MemoryCacheLifecycle { disk_cache: None }
387 }
388}
389
390impl Lifecycle<CacheKey, CacheEntry> for MemoryCacheLifecycle {
391 type RequestState = ();
392
393 fn is_pinned(&self, key: &CacheKey, val: &CacheEntry) -> bool {
396 let pinned = val
397 .try_read()
398 .map(|cached_resources| cached_resources.iter().any(|resource| !resource.is_done()))
399 .unwrap_or(true);
400 info!("Key {key:?} is pinned",);
401 pinned
402 }
403
404 fn on_evict(&self, _state: &mut Self::RequestState, key: CacheKey, value: CacheEntry) {
405 info!("Evicting {key:?} from memory cache");
406 if let Some(disk_cache_data) = &self.disk_cache {
407 let disk_cache_data = disk_cache_data.clone();
408 tokio::spawn(async move { disk_cache_data.store(key, value).await });
409 }
410 }
411}
412
413fn is_cacheable_by_default(status_code: StatusCode) -> bool {
415 matches!(
416 status_code.as_u16(),
417 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
418 )
419}
420
421fn response_is_cacheable(metadata: &Metadata) -> bool {
424 if metadata.status == StatusCode::NOT_MODIFIED {
426 return false;
427 }
428
429 let headers = metadata.headers.as_ref().unwrap();
432 if headers
433 .typed_get::<Vary>()
434 .is_some_and(|vary| vary.is_any())
435 {
436 return false;
437 }
438
439 let mut is_cacheable = false;
443 if headers.contains_key(header::EXPIRES) ||
444 headers.contains_key(header::LAST_MODIFIED) ||
445 headers.contains_key(header::ETAG)
446 {
447 is_cacheable = true;
448 }
449 if let Some(ref directive) = headers.typed_get::<CacheControl>() {
450 if directive.no_store() {
451 return false;
452 }
453 if directive.public() ||
454 directive.s_max_age().is_some() ||
455 directive.max_age().is_some() ||
456 directive.no_cache()
457 {
458 return true;
460 }
461 }
462 if let Some(pragma) = headers.typed_get::<Pragma>() &&
463 pragma.is_no_cache()
464 {
465 return false;
466 }
467
468 is_cacheable
469}
470
471fn calculate_response_age(response: &Response) -> ApproxDuration {
474 response
476 .headers
477 .get(header::AGE)
478 .and_then(|age_header| age_header.to_str().ok())
479 .and_then(|age_string| age_string.parse::<u64>().ok())
480 .map(ApproxDuration::from_secs)
481 .unwrap_or_default()
482}
483
484fn get_response_expiry(response: &Response) -> ApproxDuration {
487 let age = calculate_response_age(response);
489 let now = SystemTime::now();
490 if let Some(directives) = response.headers.typed_get::<CacheControl>() {
491 if directives.no_cache() {
492 return ApproxDuration::zero();
494 }
495 if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
496 let max_age: ApproxDuration = max_age.into();
497 return max_age.saturating_sub(age);
498 }
499 }
500 match response.headers.typed_get::<Expires>() {
501 Some(expiry) => {
502 let expiry_time: SystemTime = expiry.into();
505 return expiry_time
506 .duration_since(now)
507 .map(|duration| duration.into())
508 .unwrap_or(ApproxDuration::zero());
509 },
510 None if response.headers.contains_key(header::EXPIRES) => return ApproxDuration::zero(),
512 _ => {},
513 }
514 if let Some(ref code) = response.status.try_code() {
517 let max_heuristic = ApproxDuration::from_secs(24 * 60 * 60).saturating_sub(age);
521 let heuristic_freshness = if let Some(last_modified) =
522 response.headers.typed_get::<LastModified>()
526 {
527 let last_modified: SystemTime = last_modified.into();
530 let time_since_last_modified: ApproxDuration =
531 now.duration_since(last_modified).unwrap_or_default().into();
532
533 let raw_heuristic_calc = time_since_last_modified / 10;
535 if raw_heuristic_calc < max_heuristic {
536 raw_heuristic_calc
537 } else {
538 max_heuristic
539 }
540 } else {
541 ApproxDuration::zero()
543 };
544 if is_cacheable_by_default(*code) {
545 return heuristic_freshness;
547 }
548 if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
550 directives.public()
551 {
552 return heuristic_freshness;
553 }
554 }
555 ApproxDuration::zero()
557}
558
559fn get_stale_while_revalidate(headers: &HeaderMap) -> ApproxDuration {
563 for value in headers.get_all(header::CACHE_CONTROL) {
564 let Ok(value) = value.to_str() else {
565 continue;
566 };
567 for directive in value.split(',') {
568 let directive = directive.trim();
569 let Some((name, argument)) = directive.split_once('=') else {
570 continue;
571 };
572 if !name.trim().eq_ignore_ascii_case("stale-while-revalidate") {
573 continue;
574 }
575 let argument = argument.trim().trim_matches('"');
577 if let Ok(seconds) = argument.parse::<u64>() {
578 return ApproxDuration::from_secs(seconds);
579 }
580 }
581 }
582 ApproxDuration::zero()
583}
584
585fn request_demands_revalidation(request: &Request) -> bool {
588 if matches!(
589 request.cache_mode,
590 CacheMode::NoCache | CacheMode::Reload | CacheMode::NoStore
591 ) {
592 return true;
593 }
594 if let Some(directive) = request.headers.typed_get::<CacheControl>() {
595 if directive.no_cache() {
599 return true;
600 }
601
602 if directive.max_age() == Some(Duration::ZERO) {
603 return true;
604 }
605 }
606 false
607}
608
609fn get_expiry_adjustment_from_request_headers(
612 request: &Request,
613 expires: ApproxDuration,
614) -> ApproxDuration {
615 let Some(directive) = request.headers.typed_get::<CacheControl>() else {
616 return expires;
617 };
618
619 if let Some(max_age) = directive.max_stale() {
620 let max_age: ApproxDuration = max_age.into();
621 return expires + max_age;
622 };
623
624 let max_age: Option<ApproxDuration> = directive.max_age().map(|max_age| max_age.into());
625 match max_age {
626 Some(max_age) if expires > max_age => return ApproxDuration::zero(),
627 Some(max_age) => return expires - max_age,
628 None => {},
629 };
630
631 if let Some(min_fresh) = directive.min_fresh() {
632 let min_fresh: ApproxDuration = min_fresh.into();
633 if expires < min_fresh {
634 return ApproxDuration::zero();
635 };
636 return expires - min_fresh;
637 }
638
639 if directive.no_cache() || directive.no_store() {
640 return ApproxDuration::zero();
641 }
642
643 expires
644}
645
646fn create_cached_response(
648 request: &Request,
649 cached_resource: &CachedResource,
650 cached_headers: &HeaderMap,
651 done_chan: &mut DoneChannel,
652) -> Option<CachedResponse> {
653 debug!("creating a cached response for {:?}", request.url());
654 if cached_resource.aborted.load(Ordering::Acquire) {
655 return None;
656 }
657 let resource_timing = ResourceFetchTiming::new(request.timing_type());
658 let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
659 response.headers = cached_headers.clone();
660 response.body = cached_resource.body.clone();
661 if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
662 debug!("existing body is in progress");
663 let (done_sender, done_receiver) = unbounded();
664 *done_chan = Some((done_sender.clone(), done_receiver));
665 cached_resource.awaiting_body.lock().push(done_sender);
666 }
667 response
668 .location_url
669 .clone_from(&cached_resource.location_url);
670 response.status.clone_from(&cached_resource.status);
671 response.url_list.clone_from(&cached_resource.url_list);
672 response.referrer = request.referrer.to_url().cloned();
673 response.referrer_policy = request.referrer_policy;
674 response.aborted = cached_resource.aborted.clone();
675
676 let expires = cached_resource.expires;
677 let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
678 let Ok(time_since_validated) = SystemTime::now().duration_since(cached_resource.last_validated)
679 else {
680 return None;
681 };
682
683 let time_since_validated: ApproxDuration = time_since_validated.into();
684 let has_expired = adjusted_expires <= time_since_validated;
688
689 let stale_for = time_since_validated.saturating_sub(adjusted_expires);
694 let within_stale_while_revalidate_window = stale_for <= cached_resource.stale_while_revalidate;
695 let validation_status = if !has_expired {
696 ValidationStatus::Valid
697 } else {
698 ValidationStatus::Stale {
699 revalidate_in_background: within_stale_while_revalidate_window &&
700 !cached_resource.stale_while_revalidate.is_zero() &&
701 !request_demands_revalidation(request),
702 }
703 };
704
705 let cached_response = CachedResponse {
706 response,
707 validation_status,
708 revalidation_guard: cached_resource.revalidating.clone(),
709 };
710 Some(cached_response)
711}
712
713fn create_resource_with_bytes_from_resource(
716 bytes: &[u8],
717 resource: &CachedResource,
718) -> CachedResource {
719 CachedResource {
720 request_headers: resource.request_headers.clone(),
721 body: Arc::new(ParkingLotMutex::new(ResponseBody::Done(bytes.to_owned()))),
722 aborted: Arc::new(AtomicBool::new(false)),
723 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
724 metadata: resource.metadata.clone(),
725 location_url: resource.location_url.clone(),
726 status: StatusCode::PARTIAL_CONTENT.into(),
727 url_list: resource.url_list.clone(),
728 expires: resource.expires,
729 stale_while_revalidate: resource.stale_while_revalidate,
730 revalidating: resource.revalidating.clone(),
731 last_validated: resource.last_validated,
732 }
733}
734
735fn handle_range_request(
737 request: &Request,
738 candidates: &[&CachedResource],
739 range_spec: &Range,
740 done_chan: &mut DoneChannel,
741) -> Option<CachedResponse> {
742 let mut complete_cached_resources = candidates
743 .iter()
744 .filter(|resource| resource.status == StatusCode::OK);
745 let partial_cached_resources = candidates
746 .iter()
747 .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
748 if let Some(complete_resource) = complete_cached_resources.next() {
749 let body_len = match *complete_resource.body.lock() {
758 ResponseBody::Done(ref body) => body.len(),
759 _ => 0,
760 };
761 let bound = range_spec
762 .satisfiable_ranges(body_len.try_into().unwrap())
763 .next()
764 .unwrap();
765 match bound {
766 (Bound::Included(beginning), Bound::Included(end)) => {
767 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
768 if end == u64::MAX {
769 return None;
771 }
772 let b = beginning as usize;
773 let e = end as usize + 1;
774 let requested = body.get(b..e);
775 if let Some(bytes) = requested {
776 let new_resource =
777 create_resource_with_bytes_from_resource(bytes, complete_resource);
778 let cached_headers = new_resource.metadata.headers.lock();
779 let cached_response = create_cached_response(
780 request,
781 &new_resource,
782 &cached_headers,
783 done_chan,
784 );
785 if let Some(cached_response) = cached_response {
786 return Some(cached_response);
787 }
788 }
789 }
790 },
791 (Bound::Included(beginning), Bound::Unbounded) => {
792 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
793 let b = beginning as usize;
794 let requested = body.get(b..);
795 if let Some(bytes) = requested {
796 let new_resource =
797 create_resource_with_bytes_from_resource(bytes, complete_resource);
798 let cached_headers = new_resource.metadata.headers.lock();
799 let cached_response = create_cached_response(
800 request,
801 &new_resource,
802 &cached_headers,
803 done_chan,
804 );
805 if let Some(cached_response) = cached_response {
806 return Some(cached_response);
807 }
808 }
809 }
810 },
811 _ => return None,
812 }
813 } else {
814 for partial_resource in partial_cached_resources {
815 let headers = partial_resource.metadata.headers.lock();
816 let content_range = headers.typed_get::<ContentRange>();
817
818 let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
819 continue;
820 };
821 match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
822 (Bound::Included(beginning), Bound::Included(end)) => {
823 let (res_beginning, res_end) = match content_range {
824 Some(range) => {
825 if let Some(bytes_range) = range.bytes_range() {
826 bytes_range
827 } else {
828 continue;
829 }
830 },
831 _ => continue,
832 };
833 if res_beginning <= beginning && res_end >= end {
834 let resource_body = &*partial_resource.body.lock();
835 let requested = match resource_body {
836 ResponseBody::Done(body) => {
837 let b = beginning as usize - res_beginning as usize;
838 let e = end as usize - res_beginning as usize + 1;
839 body.get(b..e)
840 },
841 _ => continue,
842 };
843 if let Some(bytes) = requested {
844 let new_resource =
845 create_resource_with_bytes_from_resource(bytes, partial_resource);
846 let cached_response =
847 create_cached_response(request, &new_resource, &headers, done_chan);
848 if let Some(cached_response) = cached_response {
849 return Some(cached_response);
850 }
851 }
852 }
853 },
854
855 (Bound::Included(beginning), Bound::Unbounded) => {
856 let (res_beginning, res_end, total) = if let Some(range) = content_range {
857 match (range.bytes_range(), range.bytes_len()) {
858 (Some(bytes_range), Some(total)) => {
859 (bytes_range.0, bytes_range.1, total)
860 },
861 _ => continue,
862 }
863 } else {
864 continue;
865 };
866 if total == 0 {
867 continue;
869 };
870 if res_beginning <= beginning && res_end == total - 1 {
871 let resource_body = &*partial_resource.body.lock();
872 let requested = match resource_body {
873 ResponseBody::Done(body) => {
874 let from_byte = beginning as usize - res_beginning as usize;
875 body.get(from_byte..)
876 },
877 _ => continue,
878 };
879 if let Some(bytes) = requested {
880 if bytes.len() as u64 + beginning < total - 1 {
881 continue;
883 }
884 let new_resource =
885 create_resource_with_bytes_from_resource(bytes, partial_resource);
886 let cached_response =
887 create_cached_response(request, &new_resource, &headers, done_chan);
888 if let Some(cached_response) = cached_response {
889 return Some(cached_response);
890 }
891 }
892 }
893 },
894
895 _ => continue,
896 }
897 }
898 }
899
900 None
901}
902
903pub(crate) fn construct_response(
906 request: &Request,
907 done_chan: &mut DoneChannel,
908 cache_result: &[CachedResource],
909) -> Option<CachedResponse> {
910 if pref!(network_http_cache_disabled) {
911 return None;
912 }
913
914 debug!("trying to construct cache response for {:?}", request.url());
916 if request.method != Method::GET {
917 debug!("non-GET method, not caching");
919 return None;
920 }
921
922 let mut candidates: Vec<_> = cache_result
923 .iter()
924 .filter(|resource| resource.can_be_used_to_answer(request))
925 .collect();
926
927 if let Some(range_spec) = request.headers.typed_get::<Range>() {
929 return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
930 }
931
932 while let Some(cached_resource) = candidates.pop() {
933 let cached_headers = cached_resource.metadata.headers.lock();
937 let cached_response =
938 create_cached_response(request, cached_resource, &cached_headers, done_chan);
939 if let Some(cached_response) = cached_response {
940 return Some(cached_response);
941 }
942 }
943 debug!("couldn't find an appropriate response, not caching");
944 None
946}
947
948pub fn refresh(
951 request: &Request,
952 response: Response,
953 done_chan: &mut DoneChannel,
954 cached_resources: &mut [CachedResource],
955) -> Option<Response> {
956 assert_eq!(response.status, StatusCode::NOT_MODIFIED);
957
958 let cached_resource_index = cached_resources
962 .iter()
963 .rposition(|resource| resource.can_be_used_to_answer(request))?;
964
965 let mut constructed_response = if let Some(range_spec) = request.headers.typed_get::<Range>() {
966 let cached_resources: Vec<_> = cached_resources
967 .iter()
968 .filter(|resource| resource.can_be_used_to_answer(request))
969 .collect();
970 handle_range_request(request, &cached_resources, &range_spec, done_chan)
971 .map(|cached_response| cached_response.response)
972 } else {
973 let cached_resource = &mut cached_resources[cached_resource_index];
978 let in_progress_channel = match &*cached_resource.body.lock() {
979 ResponseBody::Receiving(..) => Some(unbounded()),
980 ResponseBody::Empty | ResponseBody::Done(..) => None,
981 };
982 match in_progress_channel {
983 Some((done_sender, done_receiver)) => {
984 *done_chan = Some((done_sender.clone(), done_receiver));
985 cached_resource.awaiting_body.lock().push(done_sender);
986 },
987 None => *done_chan = None,
988 }
989 let resource_timing = ResourceFetchTiming::new(request.timing_type());
993 let mut constructed_response =
994 Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
995
996 constructed_response.body = cached_resource.body.clone();
997
998 constructed_response
999 .status
1000 .clone_from(&cached_resource.status);
1001 constructed_response.referrer = request.referrer.to_url().cloned();
1002 constructed_response.referrer_policy = request.referrer_policy;
1003 constructed_response
1004 .status
1005 .clone_from(&cached_resource.status);
1006 constructed_response
1007 .url_list
1008 .clone_from(&cached_resource.url_list);
1009 Some(constructed_response)
1010 };
1011
1012 if let Some(constructed_response) = constructed_response.as_mut() {
1014 let cached_resource = &mut cached_resources[cached_resource_index];
1015
1016 {
1018 let mut stored_headers = cached_resource.metadata.headers.lock();
1019 stored_headers.extend(response.headers);
1020 constructed_response.headers = stored_headers.clone();
1021 }
1022 cached_resource.expires = get_response_expiry(constructed_response);
1023 cached_resource.stale_while_revalidate =
1024 get_stale_while_revalidate(&constructed_response.headers);
1025 cached_resource.last_validated = SystemTime::now();
1026 }
1027
1028 constructed_response
1029}
1030
1031pub(crate) fn invalidate_cached_resources(cached_resources: &mut [CachedResource]) {
1032 for cached_resource in cached_resources.iter_mut() {
1033 cached_resource.expires = ApproxDuration::zero();
1034 }
1035}
1036
1037fn resolve_location_url(
1038 request: &Request,
1039 response: &Response,
1040 header_name: header::HeaderName,
1041) -> Option<ServoUrl> {
1042 response
1043 .headers
1044 .get(header_name)
1045 .and_then(|value| value.to_str().ok())
1046 .and_then(|location| request.current_url().join(location).ok())
1047}
1048
1049impl HttpCache {
1050 pub(crate) async fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
1054 let entry_key = CacheKey::new(request);
1055
1056 let cached_resources = match self.entries.get(&entry_key) {
1057 None => return,
1058 Some(resources) => resources,
1059 };
1060
1061 let actual_response = response.actual_response();
1062
1063 let lock = cached_resources.read().await;
1066 let relevant_cached_resources = lock.iter().filter(|resource| {
1067 if actual_response.is_network_error() {
1068 return *resource.body.lock() == ResponseBody::Empty;
1069 }
1070 resource.status == actual_response.status
1071 });
1072
1073 for cached_resource in relevant_cached_resources {
1074 let mut awaiting_consumers = cached_resource.awaiting_body.lock();
1075 if awaiting_consumers.is_empty() {
1076 continue;
1077 }
1078 let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
1079 Data::Cancelled
1084 } else {
1085 match *cached_resource.body.lock() {
1086 ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
1087 ResponseBody::Receiving(_) => {
1088 continue;
1089 },
1090 }
1091 };
1092 for done_sender in awaiting_consumers.drain(..) {
1093 let _ = done_sender.send(to_send.clone());
1094 }
1095 }
1096 }
1097
1098 pub(crate) fn cache_entry_descriptors(&self) -> Vec<CacheEntryDescriptor> {
1100 self.entries
1101 .iter()
1102 .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string()))
1103 .collect()
1104 }
1105
1106 pub(crate) fn clear(&self) {
1108 self.entries.clear();
1109 if let Some(disk_cache) = &self.disk_cache {
1110 disk_cache.clear();
1111 }
1112 }
1113
1114 pub async fn store(&self, request: &Request, response: &Response) {
1116 let guard = self.get_or_guard(CacheKey::new(request)).await;
1117 guard.insert(request, response);
1118 }
1119
1120 pub async fn construct_response(
1122 &self,
1123 request: &Request,
1124 done_chan: &mut DoneChannel,
1125 ) -> Option<Response> {
1126 let entry = self.entries.get(&CacheKey::new(request))?;
1127 let cached_resources = entry.read().await;
1128 construct_response(request, done_chan, cached_resources.as_slice())
1129 .map(|cached| cached.response)
1130 }
1131
1132 #[cfg(feature = "test-util")]
1135 pub async fn construct_response_freshness(
1136 &self,
1137 request: &Request,
1138 done_chan: &mut DoneChannel,
1139 ) -> Option<ValidationStatus> {
1140 let entry = self.entries.get(&CacheKey::new(request))?;
1141 let cached_resources = entry.read().await;
1142 construct_response(request, done_chan, cached_resources.as_slice())
1143 .map(|cached| cached.validation_status)
1144 }
1145
1146 pub(crate) async fn invalidate_related_urls(
1148 &self,
1149 request: &Request,
1150 response: &Response,
1151 skip_key: &CacheKey,
1152 ) {
1153 for header_name in &[header::LOCATION, header::CONTENT_LOCATION] {
1154 if let Some(location_url) = resolve_location_url(request, response, header_name.clone())
1155 {
1156 let location_key = CacheKey::from_url(location_url);
1157 if &location_key != skip_key {
1158 self.invalidate_entry(&location_key).await;
1159 }
1160 }
1161 }
1162 }
1163
1164 async fn invalidate_entry(&self, key: &CacheKey) {
1165 if let Some(entry) = self.entries.get(key) {
1166 let mut guarded_resources = entry.write().await;
1167 invalidate_cached_resources(guarded_resources.as_mut_slice());
1168 }
1169 }
1170
1171 #[servo_tracing::instrument(skip(self))]
1174 pub async fn get_or_guard(&self, entry_key: CacheKey) -> CachedResourcesOrGuard<'_> {
1175 let guard_or_value = self.entries.get_value_or_guard_async(&entry_key).await;
1176 if let Ok(value) = guard_or_value {
1177 return CachedResourcesOrGuard::Value(value.write_owned().await);
1178 }
1179 let guard = guard_or_value.unwrap_err();
1180
1181 if let Some(disk_cache) = &self.disk_cache {
1182 if let Some(response) = disk_cache.get(entry_key).await {
1183 if guard.insert(response.clone()).is_err() {
1184 error!(
1185 "Cache guard is invalid. This should not happen. Cache will be in inconsistent state."
1186 );
1187 }
1188 let response = response.write_owned().await;
1189 CachedResourcesOrGuard::Value(response)
1190 } else {
1191 CachedResourcesOrGuard::Guard(guard)
1192 }
1193 } else {
1194 CachedResourcesOrGuard::Guard(guard)
1195 }
1196 }
1197}
1198
1199pub enum CachedResourcesOrGuard<'a> {
1202 Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
1204 Guard(QuickCachePlaceholderGuard<'a>),
1206}
1207
1208impl<'a> CachedResourcesOrGuard<'a> {
1209 pub fn insert(self, request: &Request, response: &Response) {
1211 if pref!(network_http_cache_disabled) {
1212 return;
1213 }
1214
1215 if request.method != Method::GET {
1216 return;
1218 }
1219 if request.headers.contains_key(header::AUTHORIZATION) {
1220 return;
1227 };
1228 let Ok(metadata): Result<Metadata, _> = response.metadata().map(Into::into) else {
1229 return;
1230 };
1231 if !response_is_cacheable(&metadata) {
1232 return;
1233 }
1234 let expiry = get_response_expiry(response);
1235 let stale_while_revalidate = get_stale_while_revalidate(&response.headers);
1236 let cacheable_metadata = CachedMetadata {
1237 headers: Arc::new(ParkingLotMutex::new(response.headers.clone().into())),
1238 final_url: metadata.final_url,
1239 content_type: metadata.content_type,
1240 charset: metadata.charset,
1241 status: metadata.status,
1242 };
1243 let entry_resource = CachedResource {
1244 request_headers: Arc::new(ParkingLotMutex::new(request.headers.clone().into())),
1245 body: response.body.clone(),
1246 aborted: response.aborted.clone(),
1247 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
1248 metadata: cacheable_metadata,
1249 location_url: response.location_url.clone(),
1250 status: response.status.clone(),
1251 url_list: response.url_list.clone(),
1252 expires: expiry,
1253 stale_while_revalidate,
1254 revalidating: StdArc::new(AtomicBool::new(false)),
1255 last_validated: SystemTime::now(),
1256 };
1257
1258 match self {
1259 CachedResourcesOrGuard::Value(mut cached_resources) => {
1260 if response.status == StatusCode::OK {
1264 cached_resources.retain(|cached_resource| {
1265 cached_resource.status != StatusCode::OK ||
1266 !cached_resource.is_done() ||
1267 !cached_resource.vary_header_matches(&request.headers)
1268 });
1269 }
1270
1271 cached_resources.push(entry_resource);
1272 },
1273 CachedResourcesOrGuard::Guard(placeholder_guard) => {
1274 if placeholder_guard
1275 .insert(std::sync::Arc::new(TokioRwLock::new(vec![entry_resource])))
1276 .is_err()
1277 {
1278 error!("Could not insert into cache");
1279 }
1280 },
1281 }
1282 }
1283
1284 pub fn try_as_mut(&mut self) -> Option<&mut Vec<CachedResource>> {
1286 match self {
1287 CachedResourcesOrGuard::Value(owned_rw_lock_write_guard) => {
1288 Some(owned_rw_lock_write_guard.as_mut())
1289 },
1290 CachedResourcesOrGuard::Guard(_) => None,
1291 }
1292 }
1293}