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, info};
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 std::fmt::Debug for HttpCache {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 f.debug_list().entries(self.entries.iter()).finish()
265 }
266}
267
268impl MallocSizeOf for HttpCache {
269 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
270 self.entries
271 .iter()
272 .map(|(_key, entry)| entry.blocking_read().size_of(ops))
273 .sum::<usize>() +
274 self.disk_cache
275 .as_ref()
276 .map(|data| data.size_of(ops))
277 .unwrap_or(0)
278 }
279}
280
281impl HttpCache {
282 pub fn new(assignment: HttpCacheAssignment) -> Self {
284 let size = pref!(network_http_cache_size)
285 .try_into()
286 .expect("http_cache_size needs to fit into u64");
287 let (disk_cache, lifecycle) = DiskCache::new(assignment);
288 let memory_cache = Cache::with(
289 size,
290 size as u64,
291 UnitWeighter,
292 DefaultHashBuilder::default(),
293 lifecycle,
294 );
295
296 Self {
297 entries: memory_cache,
298 disk_cache,
299 }
300 }
301
302 #[allow(unused, clippy::len_without_is_empty)]
303 pub fn len(&self) -> usize {
305 self.entries.len()
306 }
307}
308
309#[derive(Clone)]
310pub struct MemoryCacheLifecycle {
313 pub(crate) disk_cache: Option<StdArc<DiskCache>>,
314}
315
316impl MemoryCacheLifecycle {
317 pub(crate) fn empty() -> MemoryCacheLifecycle {
318 MemoryCacheLifecycle { disk_cache: None }
319 }
320}
321
322impl Lifecycle<CacheKey, CacheEntry> for MemoryCacheLifecycle {
323 type RequestState = ();
324
325 fn is_pinned(&self, key: &CacheKey, val: &CacheEntry) -> bool {
328 let pinned = val
329 .try_read()
330 .map(|cached_resources| cached_resources.iter().any(|resource| !resource.is_done()))
331 .unwrap_or(true);
332 info!("Key {key:?} is pinned",);
333 pinned
334 }
335
336 fn on_evict(&self, _state: &mut Self::RequestState, key: CacheKey, value: CacheEntry) {
337 info!("Evicting {key:?} from memory cache");
338 if let Some(disk_cache_data) = &self.disk_cache {
339 let disk_cache_data = disk_cache_data.clone();
340 tokio::spawn(async move { disk_cache_data.store(key, value).await });
341 }
342 }
343}
344
345fn is_cacheable_by_default(status_code: StatusCode) -> bool {
347 matches!(
348 status_code.as_u16(),
349 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
350 )
351}
352
353fn response_is_cacheable(metadata: &Metadata) -> bool {
356 let mut is_cacheable = false;
360 let headers = metadata.headers.as_ref().unwrap();
361 if headers.contains_key(header::EXPIRES) ||
362 headers.contains_key(header::LAST_MODIFIED) ||
363 headers.contains_key(header::ETAG)
364 {
365 is_cacheable = true;
366 }
367 if let Some(ref directive) = headers.typed_get::<CacheControl>() {
368 if directive.no_store() {
369 return false;
370 }
371 if directive.public() ||
372 directive.s_max_age().is_some() ||
373 directive.max_age().is_some() ||
374 directive.no_cache()
375 {
376 return true;
378 }
379 }
380 if let Some(pragma) = headers.typed_get::<Pragma>() &&
381 pragma.is_no_cache()
382 {
383 return false;
384 }
385 is_cacheable
386}
387
388fn calculate_response_age(response: &Response) -> ApproxDuration {
391 response
393 .headers
394 .get(header::AGE)
395 .and_then(|age_header| age_header.to_str().ok())
396 .and_then(|age_string| age_string.parse::<u64>().ok())
397 .map(ApproxDuration::from_secs)
398 .unwrap_or_default()
399}
400
401fn get_response_expiry(response: &Response) -> ApproxDuration {
404 let age = calculate_response_age(response);
406 let now = SystemTime::now();
407 if let Some(directives) = response.headers.typed_get::<CacheControl>() {
408 if directives.no_cache() {
409 return ApproxDuration::zero();
411 }
412 if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
413 let max_age: ApproxDuration = max_age.into();
414 return max_age.saturating_sub(age);
415 }
416 }
417 match response.headers.typed_get::<Expires>() {
418 Some(expiry) => {
419 let expiry_time: SystemTime = expiry.into();
422 return expiry_time
423 .duration_since(now)
424 .map(|duration| duration.into())
425 .unwrap_or(ApproxDuration::zero());
426 },
427 None if response.headers.contains_key(header::EXPIRES) => return ApproxDuration::zero(),
429 _ => {},
430 }
431 if let Some(ref code) = response.status.try_code() {
434 let max_heuristic = ApproxDuration::from_secs(24 * 60 * 60).saturating_sub(age);
438 let heuristic_freshness = if let Some(last_modified) =
439 response.headers.typed_get::<LastModified>()
443 {
444 let last_modified: SystemTime = last_modified.into();
447 let time_since_last_modified: ApproxDuration =
448 now.duration_since(last_modified).unwrap_or_default().into();
449
450 let raw_heuristic_calc = time_since_last_modified / 10;
452 if raw_heuristic_calc < max_heuristic {
453 raw_heuristic_calc
454 } else {
455 max_heuristic
456 }
457 } else {
458 ApproxDuration::zero()
460 };
461 if is_cacheable_by_default(*code) {
462 return heuristic_freshness;
464 }
465 if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
467 directives.public()
468 {
469 return heuristic_freshness;
470 }
471 }
472 ApproxDuration::zero()
474}
475
476fn get_stale_while_revalidate(headers: &HeaderMap) -> ApproxDuration {
480 for value in headers.get_all(header::CACHE_CONTROL) {
481 let Ok(value) = value.to_str() else {
482 continue;
483 };
484 for directive in value.split(',') {
485 let directive = directive.trim();
486 let Some((name, argument)) = directive.split_once('=') else {
487 continue;
488 };
489 if !name.trim().eq_ignore_ascii_case("stale-while-revalidate") {
490 continue;
491 }
492 let argument = argument.trim().trim_matches('"');
494 if let Ok(seconds) = argument.parse::<u64>() {
495 return ApproxDuration::from_secs(seconds);
496 }
497 }
498 }
499 ApproxDuration::zero()
500}
501
502fn request_demands_revalidation(request: &Request) -> bool {
505 if matches!(
506 request.cache_mode,
507 CacheMode::NoCache | CacheMode::Reload | CacheMode::NoStore
508 ) {
509 return true;
510 }
511 if let Some(directive) = request.headers.typed_get::<CacheControl>() {
512 if directive.no_cache() {
516 return true;
517 }
518
519 if directive.max_age() == Some(Duration::ZERO) {
520 return true;
521 }
522 }
523 false
524}
525
526fn get_expiry_adjustment_from_request_headers(
529 request: &Request,
530 expires: ApproxDuration,
531) -> ApproxDuration {
532 let Some(directive) = request.headers.typed_get::<CacheControl>() else {
533 return expires;
534 };
535
536 if let Some(max_age) = directive.max_stale() {
537 let max_age: ApproxDuration = max_age.into();
538 return expires + max_age;
539 };
540
541 let max_age: Option<ApproxDuration> = directive.max_age().map(|max_age| max_age.into());
542 match max_age {
543 Some(max_age) if expires > max_age => return ApproxDuration::zero(),
544 Some(max_age) => return expires - max_age,
545 None => {},
546 };
547
548 if let Some(min_fresh) = directive.min_fresh() {
549 let min_fresh: ApproxDuration = min_fresh.into();
550 if expires < min_fresh {
551 return ApproxDuration::zero();
552 };
553 return expires - min_fresh;
554 }
555
556 if directive.no_cache() || directive.no_store() {
557 return ApproxDuration::zero();
558 }
559
560 expires
561}
562
563fn create_cached_response(
565 request: &Request,
566 cached_resource: &CachedResource,
567 cached_headers: &HeaderMap,
568 done_chan: &mut DoneChannel,
569) -> Option<CachedResponse> {
570 debug!("creating a cached response for {:?}", request.url());
571 if cached_resource.aborted.load(Ordering::Acquire) {
572 return None;
573 }
574 let resource_timing = ResourceFetchTiming::new(request.timing_type());
575 let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
576 response.headers = cached_headers.clone();
577 response.body = cached_resource.body.clone();
578 if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
579 debug!("existing body is in progress");
580 let (done_sender, done_receiver) = unbounded();
581 *done_chan = Some((done_sender.clone(), done_receiver));
582 cached_resource.awaiting_body.lock().push(done_sender);
583 }
584 response
585 .location_url
586 .clone_from(&cached_resource.location_url);
587 response.status.clone_from(&cached_resource.status);
588 response.url_list.clone_from(&cached_resource.url_list);
589 response.referrer = request.referrer.to_url().cloned();
590 response.referrer_policy = request.referrer_policy;
591 response.aborted = cached_resource.aborted.clone();
592
593 let expires = cached_resource.expires;
594 let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
595 let Ok(time_since_validated) = SystemTime::now().duration_since(cached_resource.last_validated)
596 else {
597 return None;
598 };
599
600 let time_since_validated: ApproxDuration = time_since_validated.into();
601 let has_expired = adjusted_expires <= time_since_validated;
605
606 let stale_for = time_since_validated.saturating_sub(adjusted_expires);
611 let within_stale_while_revalidate_window = stale_for <= cached_resource.stale_while_revalidate;
612 let validation_status = if !has_expired {
613 ValidationStatus::Valid
614 } else {
615 ValidationStatus::Stale {
616 revalidate_in_background: within_stale_while_revalidate_window &&
617 !cached_resource.stale_while_revalidate.is_zero() &&
618 !request_demands_revalidation(request),
619 }
620 };
621
622 let cached_response = CachedResponse {
623 response,
624 validation_status,
625 revalidation_guard: cached_resource.revalidating.clone(),
626 };
627 Some(cached_response)
628}
629
630fn create_resource_with_bytes_from_resource(
633 bytes: &[u8],
634 resource: &CachedResource,
635) -> CachedResource {
636 CachedResource {
637 request_headers: resource.request_headers.clone(),
638 body: Arc::new(ParkingLotMutex::new(ResponseBody::Done(bytes.to_owned()))),
639 aborted: Arc::new(AtomicBool::new(false)),
640 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
641 metadata: resource.metadata.clone(),
642 location_url: resource.location_url.clone(),
643 status: StatusCode::PARTIAL_CONTENT.into(),
644 url_list: resource.url_list.clone(),
645 expires: resource.expires,
646 stale_while_revalidate: resource.stale_while_revalidate,
647 revalidating: resource.revalidating.clone(),
648 last_validated: resource.last_validated,
649 }
650}
651
652fn handle_range_request(
654 request: &Request,
655 candidates: &[&CachedResource],
656 range_spec: &Range,
657 done_chan: &mut DoneChannel,
658) -> Option<CachedResponse> {
659 let mut complete_cached_resources = candidates
660 .iter()
661 .filter(|resource| resource.status == StatusCode::OK);
662 let partial_cached_resources = candidates
663 .iter()
664 .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
665 if let Some(complete_resource) = complete_cached_resources.next() {
666 let body_len = match *complete_resource.body.lock() {
675 ResponseBody::Done(ref body) => body.len(),
676 _ => 0,
677 };
678 let bound = range_spec
679 .satisfiable_ranges(body_len.try_into().unwrap())
680 .next()
681 .unwrap();
682 match bound {
683 (Bound::Included(beginning), Bound::Included(end)) => {
684 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
685 if end == u64::MAX {
686 return None;
688 }
689 let b = beginning as usize;
690 let e = end as usize + 1;
691 let requested = body.get(b..e);
692 if let Some(bytes) = requested {
693 let new_resource =
694 create_resource_with_bytes_from_resource(bytes, complete_resource);
695 let cached_headers = new_resource.metadata.headers.lock();
696 let cached_response = create_cached_response(
697 request,
698 &new_resource,
699 &cached_headers,
700 done_chan,
701 );
702 if let Some(cached_response) = cached_response {
703 return Some(cached_response);
704 }
705 }
706 }
707 },
708 (Bound::Included(beginning), Bound::Unbounded) => {
709 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
710 let b = beginning as usize;
711 let requested = body.get(b..);
712 if let Some(bytes) = requested {
713 let new_resource =
714 create_resource_with_bytes_from_resource(bytes, complete_resource);
715 let cached_headers = new_resource.metadata.headers.lock();
716 let cached_response = create_cached_response(
717 request,
718 &new_resource,
719 &cached_headers,
720 done_chan,
721 );
722 if let Some(cached_response) = cached_response {
723 return Some(cached_response);
724 }
725 }
726 }
727 },
728 _ => return None,
729 }
730 } else {
731 for partial_resource in partial_cached_resources {
732 let headers = partial_resource.metadata.headers.lock();
733 let content_range = headers.typed_get::<ContentRange>();
734
735 let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
736 continue;
737 };
738 match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
739 (Bound::Included(beginning), Bound::Included(end)) => {
740 let (res_beginning, res_end) = match content_range {
741 Some(range) => {
742 if let Some(bytes_range) = range.bytes_range() {
743 bytes_range
744 } else {
745 continue;
746 }
747 },
748 _ => continue,
749 };
750 if res_beginning <= beginning && res_end >= end {
751 let resource_body = &*partial_resource.body.lock();
752 let requested = match resource_body {
753 ResponseBody::Done(body) => {
754 let b = beginning as usize - res_beginning as usize;
755 let e = end as usize - res_beginning as usize + 1;
756 body.get(b..e)
757 },
758 _ => continue,
759 };
760 if let Some(bytes) = requested {
761 let new_resource =
762 create_resource_with_bytes_from_resource(bytes, partial_resource);
763 let cached_response =
764 create_cached_response(request, &new_resource, &headers, done_chan);
765 if let Some(cached_response) = cached_response {
766 return Some(cached_response);
767 }
768 }
769 }
770 },
771
772 (Bound::Included(beginning), Bound::Unbounded) => {
773 let (res_beginning, res_end, total) = if let Some(range) = content_range {
774 match (range.bytes_range(), range.bytes_len()) {
775 (Some(bytes_range), Some(total)) => {
776 (bytes_range.0, bytes_range.1, total)
777 },
778 _ => continue,
779 }
780 } else {
781 continue;
782 };
783 if total == 0 {
784 continue;
786 };
787 if res_beginning <= beginning && res_end == total - 1 {
788 let resource_body = &*partial_resource.body.lock();
789 let requested = match resource_body {
790 ResponseBody::Done(body) => {
791 let from_byte = beginning as usize - res_beginning as usize;
792 body.get(from_byte..)
793 },
794 _ => continue,
795 };
796 if let Some(bytes) = requested {
797 if bytes.len() as u64 + beginning < total - 1 {
798 continue;
800 }
801 let new_resource =
802 create_resource_with_bytes_from_resource(bytes, partial_resource);
803 let cached_response =
804 create_cached_response(request, &new_resource, &headers, done_chan);
805 if let Some(cached_response) = cached_response {
806 return Some(cached_response);
807 }
808 }
809 }
810 },
811
812 _ => continue,
813 }
814 }
815 }
816
817 None
818}
819
820pub(crate) fn construct_response(
823 request: &Request,
824 done_chan: &mut DoneChannel,
825 cache_result: &[CachedResource],
826) -> Option<CachedResponse> {
827 if pref!(network_http_cache_disabled) {
828 return None;
829 }
830
831 debug!("trying to construct cache response for {:?}", request.url());
833 if request.method != Method::GET {
834 debug!("non-GET method, not caching");
836 return None;
837 }
838
839 let resources = cache_result
840 .iter()
841 .filter(|r| !r.aborted.load(Ordering::Relaxed));
842 let mut candidates = vec![];
843 for cached_resource in resources {
844 let mut can_be_constructed = true;
845 let cached_headers = cached_resource.metadata.headers.lock();
846 let original_request_headers = cached_resource.request_headers.lock();
847 if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
848 if vary_value.is_any() {
849 debug!("vary value is any, not caching");
850 can_be_constructed = false
851 } else {
852 for vary_val in vary_value.iter_strs() {
855 match request.headers.get(vary_val) {
856 Some(header_data) => {
857 if let Some(original_header_data) =
859 original_request_headers.get(vary_val)
860 {
861 if original_header_data != header_data {
864 debug!("headers don't match, not caching");
865 can_be_constructed = false;
866 break;
867 }
868 }
869 },
870 None => {
871 can_be_constructed = original_request_headers.get(vary_val).is_none();
875 if !can_be_constructed {
876 debug!("vary header present, not caching");
877 }
878 },
879 }
880 if !can_be_constructed {
881 break;
882 }
883 }
884 }
885 }
886 if can_be_constructed {
887 candidates.push(cached_resource);
888 }
889 }
890 if let Some(range_spec) = request.headers.typed_get::<Range>() {
892 return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
893 }
894 while let Some(cached_resource) = candidates.pop() {
895 match cached_resource.status.try_code() {
907 Some(ref code) => {
908 if *code == StatusCode::PARTIAL_CONTENT {
909 continue;
910 }
911 },
912 None => continue,
913 }
914 let cached_headers = cached_resource.metadata.headers.lock();
918 let cached_response =
919 create_cached_response(request, cached_resource, &cached_headers, done_chan);
920 if let Some(cached_response) = cached_response {
921 return Some(cached_response);
922 }
923 }
924 debug!("couldn't find an appropriate response, not caching");
925 None
927}
928
929pub fn refresh(
932 request: &Request,
933 response: Response,
934 done_chan: &mut DoneChannel,
935 cached_resources: &mut [CachedResource],
936) -> Option<Response> {
937 assert_eq!(response.status, StatusCode::NOT_MODIFIED);
938
939 let cached_resource = cached_resources.iter_mut().next()?;
940
941 let mut constructed_response = if let Some(range_spec) = request.headers.typed_get::<Range>() {
942 handle_range_request(request, &[cached_resource], &range_spec, done_chan)
943 .map(|cached_response| cached_response.response)
944 } else {
945 let in_progress_channel = match &*cached_resource.body.lock() {
950 ResponseBody::Receiving(..) => Some(unbounded()),
951 ResponseBody::Empty | ResponseBody::Done(..) => None,
952 };
953 match in_progress_channel {
954 Some((done_sender, done_receiver)) => {
955 *done_chan = Some((done_sender.clone(), done_receiver));
956 cached_resource.awaiting_body.lock().push(done_sender);
957 },
958 None => *done_chan = None,
959 }
960 let resource_timing = ResourceFetchTiming::new(request.timing_type());
964 let mut constructed_response =
965 Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
966
967 constructed_response.body = cached_resource.body.clone();
968
969 constructed_response
970 .status
971 .clone_from(&cached_resource.status);
972 constructed_response.referrer = request.referrer.to_url().cloned();
973 constructed_response.referrer_policy = request.referrer_policy;
974 constructed_response
975 .status
976 .clone_from(&cached_resource.status);
977 constructed_response
978 .url_list
979 .clone_from(&cached_resource.url_list);
980 Some(constructed_response)
981 };
982
983 if let Some(constructed_response) = constructed_response.as_mut() {
985 {
987 let mut stored_headers = cached_resource.metadata.headers.lock();
988 stored_headers.extend(response.headers);
989 constructed_response.headers = stored_headers.clone();
990 }
991 cached_resource.expires = get_response_expiry(constructed_response);
992 cached_resource.stale_while_revalidate =
993 get_stale_while_revalidate(&constructed_response.headers);
994 cached_resource.last_validated = SystemTime::now();
995 }
996
997 constructed_response
998}
999
1000pub(crate) fn invalidate_cached_resources(cached_resources: &mut [CachedResource]) {
1001 for cached_resource in cached_resources.iter_mut() {
1002 cached_resource.expires = ApproxDuration::zero();
1003 }
1004}
1005
1006fn resolve_location_url(
1007 request: &Request,
1008 response: &Response,
1009 header_name: header::HeaderName,
1010) -> Option<ServoUrl> {
1011 response
1012 .headers
1013 .get(header_name)
1014 .and_then(|value| value.to_str().ok())
1015 .and_then(|location| request.current_url().join(location).ok())
1016}
1017
1018impl HttpCache {
1019 pub(crate) async fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
1023 let entry_key = CacheKey::new(request);
1024
1025 let cached_resources = match self.entries.get(&entry_key) {
1026 None => return,
1027 Some(resources) => resources,
1028 };
1029
1030 let actual_response = response.actual_response();
1031
1032 let lock = cached_resources.read().await;
1035 let relevant_cached_resources = lock.iter().filter(|resource| {
1036 if actual_response.is_network_error() {
1037 return *resource.body.lock() == ResponseBody::Empty;
1038 }
1039 resource.status == actual_response.status
1040 });
1041
1042 for cached_resource in relevant_cached_resources {
1043 let mut awaiting_consumers = cached_resource.awaiting_body.lock();
1044 if awaiting_consumers.is_empty() {
1045 continue;
1046 }
1047 let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
1048 Data::Cancelled
1053 } else {
1054 match *cached_resource.body.lock() {
1055 ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
1056 ResponseBody::Receiving(_) => {
1057 continue;
1058 },
1059 }
1060 };
1061 for done_sender in awaiting_consumers.drain(..) {
1062 let _ = done_sender.send(to_send.clone());
1063 }
1064 }
1065 }
1066
1067 pub(crate) fn cache_entry_descriptors(&self) -> Vec<CacheEntryDescriptor> {
1069 self.entries
1070 .iter()
1071 .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string()))
1072 .collect()
1073 }
1074
1075 pub(crate) fn clear(&self) {
1077 self.entries.clear();
1078 if let Some(disk_cache) = &self.disk_cache {
1079 disk_cache.clear();
1080 }
1081 }
1082
1083 pub async fn store(&self, request: &Request, response: &Response) {
1085 let guard = self.get_or_guard(CacheKey::new(request)).await;
1086 guard.insert(request, response);
1087 }
1088
1089 pub async fn construct_response(
1091 &self,
1092 request: &Request,
1093 done_chan: &mut DoneChannel,
1094 ) -> Option<Response> {
1095 let entry = self.entries.get(&CacheKey::new(request))?;
1096 let cached_resources = entry.read().await;
1097 construct_response(request, done_chan, cached_resources.as_slice())
1098 .map(|cached| cached.response)
1099 }
1100
1101 #[cfg(feature = "test-util")]
1104 pub async fn construct_response_freshness(
1105 &self,
1106 request: &Request,
1107 done_chan: &mut DoneChannel,
1108 ) -> Option<ValidationStatus> {
1109 let entry = self.entries.get(&CacheKey::new(request))?;
1110 let cached_resources = entry.read().await;
1111 construct_response(request, done_chan, cached_resources.as_slice())
1112 .map(|cached| cached.validation_status)
1113 }
1114
1115 pub(crate) async fn invalidate_related_urls(
1117 &self,
1118 request: &Request,
1119 response: &Response,
1120 skip_key: &CacheKey,
1121 ) {
1122 for header_name in &[header::LOCATION, header::CONTENT_LOCATION] {
1123 if let Some(location_url) = resolve_location_url(request, response, header_name.clone())
1124 {
1125 let location_key = CacheKey::from_url(location_url);
1126 if &location_key != skip_key {
1127 self.invalidate_entry(&location_key).await;
1128 }
1129 }
1130 }
1131 }
1132
1133 async fn invalidate_entry(&self, key: &CacheKey) {
1134 if let Some(entry) = self.entries.get(key) {
1135 let mut guarded_resources = entry.write().await;
1136 invalidate_cached_resources(guarded_resources.as_mut_slice());
1137 }
1138 }
1139
1140 #[servo_tracing::instrument(skip(self))]
1143 pub async fn get_or_guard(&self, entry_key: CacheKey) -> CachedResourcesOrGuard<'_> {
1144 let guard_or_value = self.entries.get_value_or_guard_async(&entry_key).await;
1145 if let Ok(value) = guard_or_value {
1146 return CachedResourcesOrGuard::Value(value.write_owned().await);
1147 }
1148 let guard = guard_or_value.unwrap_err();
1149
1150 if let Some(disk_cache) = &self.disk_cache {
1151 if let Some(response) = disk_cache.get(entry_key).await {
1152 if guard.insert(response.clone()).is_err() {
1153 error!(
1154 "Cache guard is invalid. This should not happen. Cache will be in inconsistent state."
1155 );
1156 }
1157 let response = response.write_owned().await;
1158 CachedResourcesOrGuard::Value(response)
1159 } else {
1160 CachedResourcesOrGuard::Guard(guard)
1161 }
1162 } else {
1163 CachedResourcesOrGuard::Guard(guard)
1164 }
1165 }
1166}
1167
1168pub enum CachedResourcesOrGuard<'a> {
1171 Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
1173 Guard(QuickCachePlaceholderGuard<'a>),
1175}
1176
1177impl<'a> CachedResourcesOrGuard<'a> {
1178 pub fn insert(self, request: &Request, response: &Response) {
1180 if pref!(network_http_cache_disabled) {
1181 return;
1182 }
1183
1184 if request.method != Method::GET {
1185 return;
1187 }
1188 if request.headers.contains_key(header::AUTHORIZATION) {
1189 return;
1196 };
1197 let metadata = match response.metadata() {
1198 Ok(FetchMetadata::Filtered {
1199 filtered: _,
1200 unsafe_: metadata,
1201 }) |
1202 Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
1203 _ => return,
1204 };
1205 if !response_is_cacheable(&metadata) {
1206 return;
1207 }
1208 let expiry = get_response_expiry(response);
1209 let stale_while_revalidate = get_stale_while_revalidate(&response.headers);
1210 let cacheable_metadata = CachedMetadata {
1211 headers: Arc::new(ParkingLotMutex::new(response.headers.clone().into())),
1212 final_url: metadata.final_url,
1213 content_type: metadata.content_type.map(|v| v.0.to_string()),
1214 charset: metadata.charset,
1215 status: metadata.status,
1216 };
1217 let entry_resource = CachedResource {
1218 request_headers: Arc::new(ParkingLotMutex::new(request.headers.clone().into())),
1219 body: response.body.clone(),
1220 aborted: response.aborted.clone(),
1221 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
1222 metadata: cacheable_metadata,
1223 location_url: response.location_url.clone(),
1224 status: response.status.clone(),
1225 url_list: response.url_list.clone(),
1226 expires: expiry,
1227 stale_while_revalidate,
1228 revalidating: StdArc::new(AtomicBool::new(false)),
1229 last_validated: SystemTime::now(),
1230 };
1231
1232 match self {
1233 CachedResourcesOrGuard::Value(mut owned_rw_lock_write_guard) => {
1234 owned_rw_lock_write_guard.push(entry_resource);
1235 },
1236 CachedResourcesOrGuard::Guard(placeholder_guard) => {
1237 if placeholder_guard
1238 .insert(std::sync::Arc::new(TokioRwLock::new(vec![entry_resource])))
1239 .is_err()
1240 {
1241 error!("Could not insert into cache");
1242 }
1243 },
1244 }
1245 }
1246
1247 pub fn try_as_mut(&mut self) -> Option<&mut Vec<CachedResource>> {
1249 match self {
1250 CachedResourcesOrGuard::Value(owned_rw_lock_write_guard) => {
1251 Some(owned_rw_lock_write_guard.as_mut())
1252 },
1253 CachedResourcesOrGuard::Guard(_) => None,
1254 }
1255 }
1256}