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 is_pinned(&self, _: &CacheKey, val: &CacheEntry) -> bool {
316 val.blocking_read()
317 .iter()
318 .any(|resource| !resource.is_done())
319 }
320
321 fn on_evict(&self, _state: &mut Self::RequestState, key: CacheKey, value: CacheEntry) {
322 if let Some(disk_cache_data) = &self.disk_cache {
323 let disk_cache_data = disk_cache_data.clone();
324 tokio::spawn(async move { disk_cache_data.store(key, value).await });
325 }
326 }
327}
328
329fn is_cacheable_by_default(status_code: StatusCode) -> bool {
331 matches!(
332 status_code.as_u16(),
333 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
334 )
335}
336
337fn response_is_cacheable(metadata: &Metadata) -> bool {
340 let mut is_cacheable = false;
344 let headers = metadata.headers.as_ref().unwrap();
345 if headers.contains_key(header::EXPIRES) ||
346 headers.contains_key(header::LAST_MODIFIED) ||
347 headers.contains_key(header::ETAG)
348 {
349 is_cacheable = true;
350 }
351 if let Some(ref directive) = headers.typed_get::<CacheControl>() {
352 if directive.no_store() {
353 return false;
354 }
355 if directive.public() ||
356 directive.s_max_age().is_some() ||
357 directive.max_age().is_some() ||
358 directive.no_cache()
359 {
360 return true;
362 }
363 }
364 if let Some(pragma) = headers.typed_get::<Pragma>() &&
365 pragma.is_no_cache()
366 {
367 return false;
368 }
369 is_cacheable
370}
371
372fn calculate_response_age(response: &Response) -> ApproxDuration {
375 response
377 .headers
378 .get(header::AGE)
379 .and_then(|age_header| age_header.to_str().ok())
380 .and_then(|age_string| age_string.parse::<u64>().ok())
381 .map(ApproxDuration::from_secs)
382 .unwrap_or_default()
383}
384
385fn get_response_expiry(response: &Response) -> ApproxDuration {
388 let age = calculate_response_age(response);
390 let now = SystemTime::now();
391 if let Some(directives) = response.headers.typed_get::<CacheControl>() {
392 if directives.no_cache() {
393 return ApproxDuration::zero();
395 }
396 if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
397 let max_age: ApproxDuration = max_age.into();
398 return max_age.saturating_sub(age);
399 }
400 }
401 match response.headers.typed_get::<Expires>() {
402 Some(expiry) => {
403 let expiry_time: SystemTime = expiry.into();
406 return expiry_time
407 .duration_since(now)
408 .map(|duration| duration.into())
409 .unwrap_or(ApproxDuration::zero());
410 },
411 None if response.headers.contains_key(header::EXPIRES) => return ApproxDuration::zero(),
413 _ => {},
414 }
415 if let Some(ref code) = response.status.try_code() {
418 let max_heuristic = ApproxDuration::from_secs(24 * 60 * 60).saturating_sub(age);
422 let heuristic_freshness = if let Some(last_modified) =
423 response.headers.typed_get::<LastModified>()
427 {
428 let last_modified: SystemTime = last_modified.into();
431 let time_since_last_modified: ApproxDuration =
432 now.duration_since(last_modified).unwrap_or_default().into();
433
434 let raw_heuristic_calc = time_since_last_modified / 10;
436 if raw_heuristic_calc < max_heuristic {
437 raw_heuristic_calc
438 } else {
439 max_heuristic
440 }
441 } else {
442 ApproxDuration::zero()
444 };
445 if is_cacheable_by_default(*code) {
446 return heuristic_freshness;
448 }
449 if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
451 directives.public()
452 {
453 return heuristic_freshness;
454 }
455 }
456 ApproxDuration::zero()
458}
459
460fn get_stale_while_revalidate(headers: &HeaderMap) -> ApproxDuration {
464 for value in headers.get_all(header::CACHE_CONTROL) {
465 let Ok(value) = value.to_str() else {
466 continue;
467 };
468 for directive in value.split(',') {
469 let directive = directive.trim();
470 let Some((name, argument)) = directive.split_once('=') else {
471 continue;
472 };
473 if !name.trim().eq_ignore_ascii_case("stale-while-revalidate") {
474 continue;
475 }
476 let argument = argument.trim().trim_matches('"');
478 if let Ok(seconds) = argument.parse::<u64>() {
479 return ApproxDuration::from_secs(seconds);
480 }
481 }
482 }
483 ApproxDuration::zero()
484}
485
486fn request_demands_revalidation(request: &Request) -> bool {
489 if matches!(
490 request.cache_mode,
491 CacheMode::NoCache | CacheMode::Reload | CacheMode::NoStore
492 ) {
493 return true;
494 }
495 if let Some(directive) = request.headers.typed_get::<CacheControl>() {
496 if directive.no_cache() {
500 return true;
501 }
502
503 if directive.max_age() == Some(Duration::ZERO) {
504 return true;
505 }
506 }
507 false
508}
509
510fn get_expiry_adjustment_from_request_headers(
513 request: &Request,
514 expires: ApproxDuration,
515) -> ApproxDuration {
516 let Some(directive) = request.headers.typed_get::<CacheControl>() else {
517 return expires;
518 };
519
520 if let Some(max_age) = directive.max_stale() {
521 let max_age: ApproxDuration = max_age.into();
522 return expires + max_age;
523 };
524
525 let max_age: Option<ApproxDuration> = directive.max_age().map(|max_age| max_age.into());
526 match max_age {
527 Some(max_age) if expires > max_age => return ApproxDuration::zero(),
528 Some(max_age) => return expires - max_age,
529 None => {},
530 };
531
532 if let Some(min_fresh) = directive.min_fresh() {
533 let min_fresh: ApproxDuration = min_fresh.into();
534 if expires < min_fresh {
535 return ApproxDuration::zero();
536 };
537 return expires - min_fresh;
538 }
539
540 if directive.no_cache() || directive.no_store() {
541 return ApproxDuration::zero();
542 }
543
544 expires
545}
546
547fn create_cached_response(
549 request: &Request,
550 cached_resource: &CachedResource,
551 cached_headers: &HeaderMap,
552 done_chan: &mut DoneChannel,
553) -> Option<CachedResponse> {
554 debug!("creating a cached response for {:?}", request.url());
555 if cached_resource.aborted.load(Ordering::Acquire) {
556 return None;
557 }
558 let resource_timing = ResourceFetchTiming::new(request.timing_type());
559 let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
560 response.headers = cached_headers.clone();
561 response.body = cached_resource.body.clone();
562 if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
563 debug!("existing body is in progress");
564 let (done_sender, done_receiver) = unbounded();
565 *done_chan = Some((done_sender.clone(), done_receiver));
566 cached_resource.awaiting_body.lock().push(done_sender);
567 }
568 response
569 .location_url
570 .clone_from(&cached_resource.location_url);
571 response.status.clone_from(&cached_resource.status);
572 response.url_list.clone_from(&cached_resource.url_list);
573 response.referrer = request.referrer.to_url().cloned();
574 response.referrer_policy = request.referrer_policy;
575 response.aborted = cached_resource.aborted.clone();
576
577 let expires = cached_resource.expires;
578 let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
579 let Ok(time_since_validated) = SystemTime::now().duration_since(cached_resource.last_validated)
580 else {
581 return None;
582 };
583
584 let time_since_validated: ApproxDuration = time_since_validated.into();
585 let has_expired = adjusted_expires <= time_since_validated;
589
590 let stale_for = time_since_validated.saturating_sub(adjusted_expires);
595 let within_stale_while_revalidate_window = stale_for <= cached_resource.stale_while_revalidate;
596 let validation_status = if !has_expired {
597 ValidationStatus::Valid
598 } else {
599 ValidationStatus::Stale {
600 revalidate_in_background: within_stale_while_revalidate_window &&
601 !cached_resource.stale_while_revalidate.is_zero() &&
602 !request_demands_revalidation(request),
603 }
604 };
605
606 let cached_response = CachedResponse {
607 response,
608 validation_status,
609 revalidation_guard: cached_resource.revalidating.clone(),
610 };
611 Some(cached_response)
612}
613
614fn create_resource_with_bytes_from_resource(
617 bytes: &[u8],
618 resource: &CachedResource,
619) -> CachedResource {
620 CachedResource {
621 request_headers: resource.request_headers.clone(),
622 body: Arc::new(ParkingLotMutex::new(ResponseBody::Done(bytes.to_owned()))),
623 aborted: Arc::new(AtomicBool::new(false)),
624 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
625 metadata: resource.metadata.clone(),
626 location_url: resource.location_url.clone(),
627 status: StatusCode::PARTIAL_CONTENT.into(),
628 url_list: resource.url_list.clone(),
629 expires: resource.expires,
630 stale_while_revalidate: resource.stale_while_revalidate,
631 revalidating: resource.revalidating.clone(),
632 last_validated: resource.last_validated,
633 }
634}
635
636fn handle_range_request(
638 request: &Request,
639 candidates: &[&CachedResource],
640 range_spec: &Range,
641 done_chan: &mut DoneChannel,
642) -> Option<CachedResponse> {
643 let mut complete_cached_resources = candidates
644 .iter()
645 .filter(|resource| resource.status == StatusCode::OK);
646 let partial_cached_resources = candidates
647 .iter()
648 .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
649 if let Some(complete_resource) = complete_cached_resources.next() {
650 let body_len = match *complete_resource.body.lock() {
659 ResponseBody::Done(ref body) => body.len(),
660 _ => 0,
661 };
662 let bound = range_spec
663 .satisfiable_ranges(body_len.try_into().unwrap())
664 .next()
665 .unwrap();
666 match bound {
667 (Bound::Included(beginning), Bound::Included(end)) => {
668 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
669 if end == u64::MAX {
670 return None;
672 }
673 let b = beginning as usize;
674 let e = end as usize + 1;
675 let requested = body.get(b..e);
676 if let Some(bytes) = requested {
677 let new_resource =
678 create_resource_with_bytes_from_resource(bytes, complete_resource);
679 let cached_headers = new_resource.metadata.headers.lock();
680 let cached_response = create_cached_response(
681 request,
682 &new_resource,
683 &cached_headers,
684 done_chan,
685 );
686 if let Some(cached_response) = cached_response {
687 return Some(cached_response);
688 }
689 }
690 }
691 },
692 (Bound::Included(beginning), Bound::Unbounded) => {
693 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
694 let b = beginning as usize;
695 let requested = body.get(b..);
696 if let Some(bytes) = requested {
697 let new_resource =
698 create_resource_with_bytes_from_resource(bytes, complete_resource);
699 let cached_headers = new_resource.metadata.headers.lock();
700 let cached_response = create_cached_response(
701 request,
702 &new_resource,
703 &cached_headers,
704 done_chan,
705 );
706 if let Some(cached_response) = cached_response {
707 return Some(cached_response);
708 }
709 }
710 }
711 },
712 _ => return None,
713 }
714 } else {
715 for partial_resource in partial_cached_resources {
716 let headers = partial_resource.metadata.headers.lock();
717 let content_range = headers.typed_get::<ContentRange>();
718
719 let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
720 continue;
721 };
722 match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
723 (Bound::Included(beginning), Bound::Included(end)) => {
724 let (res_beginning, res_end) = match content_range {
725 Some(range) => {
726 if let Some(bytes_range) = range.bytes_range() {
727 bytes_range
728 } else {
729 continue;
730 }
731 },
732 _ => continue,
733 };
734 if res_beginning <= beginning && res_end >= end {
735 let resource_body = &*partial_resource.body.lock();
736 let requested = match resource_body {
737 ResponseBody::Done(body) => {
738 let b = beginning as usize - res_beginning as usize;
739 let e = end as usize - res_beginning as usize + 1;
740 body.get(b..e)
741 },
742 _ => continue,
743 };
744 if let Some(bytes) = requested {
745 let new_resource =
746 create_resource_with_bytes_from_resource(bytes, partial_resource);
747 let cached_response =
748 create_cached_response(request, &new_resource, &headers, done_chan);
749 if let Some(cached_response) = cached_response {
750 return Some(cached_response);
751 }
752 }
753 }
754 },
755
756 (Bound::Included(beginning), Bound::Unbounded) => {
757 let (res_beginning, res_end, total) = if let Some(range) = content_range {
758 match (range.bytes_range(), range.bytes_len()) {
759 (Some(bytes_range), Some(total)) => {
760 (bytes_range.0, bytes_range.1, total)
761 },
762 _ => continue,
763 }
764 } else {
765 continue;
766 };
767 if total == 0 {
768 continue;
770 };
771 if res_beginning <= beginning && res_end == total - 1 {
772 let resource_body = &*partial_resource.body.lock();
773 let requested = match resource_body {
774 ResponseBody::Done(body) => {
775 let from_byte = beginning as usize - res_beginning as usize;
776 body.get(from_byte..)
777 },
778 _ => continue,
779 };
780 if let Some(bytes) = requested {
781 if bytes.len() as u64 + beginning < total - 1 {
782 continue;
784 }
785 let new_resource =
786 create_resource_with_bytes_from_resource(bytes, partial_resource);
787 let cached_response =
788 create_cached_response(request, &new_resource, &headers, done_chan);
789 if let Some(cached_response) = cached_response {
790 return Some(cached_response);
791 }
792 }
793 }
794 },
795
796 _ => continue,
797 }
798 }
799 }
800
801 None
802}
803
804pub(crate) fn construct_response(
807 request: &Request,
808 done_chan: &mut DoneChannel,
809 cache_result: &[CachedResource],
810) -> Option<CachedResponse> {
811 if pref!(network_http_cache_disabled) {
812 return None;
813 }
814
815 debug!("trying to construct cache response for {:?}", request.url());
817 if request.method != Method::GET {
818 debug!("non-GET method, not caching");
820 return None;
821 }
822
823 let resources = cache_result
824 .iter()
825 .filter(|r| !r.aborted.load(Ordering::Relaxed));
826 let mut candidates = vec![];
827 for cached_resource in resources {
828 let mut can_be_constructed = true;
829 let cached_headers = cached_resource.metadata.headers.lock();
830 let original_request_headers = cached_resource.request_headers.lock();
831 if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
832 if vary_value.is_any() {
833 debug!("vary value is any, not caching");
834 can_be_constructed = false
835 } else {
836 for vary_val in vary_value.iter_strs() {
839 match request.headers.get(vary_val) {
840 Some(header_data) => {
841 if let Some(original_header_data) =
843 original_request_headers.get(vary_val)
844 {
845 if original_header_data != header_data {
848 debug!("headers don't match, not caching");
849 can_be_constructed = false;
850 break;
851 }
852 }
853 },
854 None => {
855 can_be_constructed = original_request_headers.get(vary_val).is_none();
859 if !can_be_constructed {
860 debug!("vary header present, not caching");
861 }
862 },
863 }
864 if !can_be_constructed {
865 break;
866 }
867 }
868 }
869 }
870 if can_be_constructed {
871 candidates.push(cached_resource);
872 }
873 }
874 if let Some(range_spec) = request.headers.typed_get::<Range>() {
876 return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
877 }
878 while let Some(cached_resource) = candidates.pop() {
879 match cached_resource.status.try_code() {
891 Some(ref code) => {
892 if *code == StatusCode::PARTIAL_CONTENT {
893 continue;
894 }
895 },
896 None => continue,
897 }
898 let cached_headers = cached_resource.metadata.headers.lock();
902 let cached_response =
903 create_cached_response(request, cached_resource, &cached_headers, done_chan);
904 if let Some(cached_response) = cached_response {
905 return Some(cached_response);
906 }
907 }
908 debug!("couldn't find an appropriate response, not caching");
909 None
911}
912
913pub fn refresh(
916 request: &Request,
917 response: Response,
918 done_chan: &mut DoneChannel,
919 cached_resources: &mut [CachedResource],
920) -> Option<Response> {
921 assert_eq!(response.status, StatusCode::NOT_MODIFIED);
922
923 let cached_resource = cached_resources.iter_mut().next()?;
924
925 let mut constructed_response = if let Some(range_spec) = request.headers.typed_get::<Range>() {
926 handle_range_request(request, &[cached_resource], &range_spec, done_chan)
927 .map(|cached_response| cached_response.response)
928 } else {
929 let in_progress_channel = match &*cached_resource.body.lock() {
934 ResponseBody::Receiving(..) => Some(unbounded()),
935 ResponseBody::Empty | ResponseBody::Done(..) => None,
936 };
937 match in_progress_channel {
938 Some((done_sender, done_receiver)) => {
939 *done_chan = Some((done_sender.clone(), done_receiver));
940 cached_resource.awaiting_body.lock().push(done_sender);
941 },
942 None => *done_chan = None,
943 }
944 let resource_timing = ResourceFetchTiming::new(request.timing_type());
948 let mut constructed_response =
949 Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
950
951 constructed_response.body = cached_resource.body.clone();
952
953 constructed_response
954 .status
955 .clone_from(&cached_resource.status);
956 constructed_response.referrer = request.referrer.to_url().cloned();
957 constructed_response.referrer_policy = request.referrer_policy;
958 constructed_response
959 .status
960 .clone_from(&cached_resource.status);
961 constructed_response
962 .url_list
963 .clone_from(&cached_resource.url_list);
964 Some(constructed_response)
965 };
966
967 if let Some(constructed_response) = constructed_response.as_mut() {
969 {
971 let mut stored_headers = cached_resource.metadata.headers.lock();
972 stored_headers.extend(response.headers);
973 constructed_response.headers = stored_headers.clone();
974 }
975 cached_resource.expires = get_response_expiry(constructed_response);
976 cached_resource.stale_while_revalidate =
977 get_stale_while_revalidate(&constructed_response.headers);
978 cached_resource.last_validated = SystemTime::now();
979 }
980
981 constructed_response
982}
983
984pub(crate) fn invalidate_cached_resources(cached_resources: &mut [CachedResource]) {
985 for cached_resource in cached_resources.iter_mut() {
986 cached_resource.expires = ApproxDuration::zero();
987 }
988}
989
990fn resolve_location_url(
991 request: &Request,
992 response: &Response,
993 header_name: header::HeaderName,
994) -> Option<ServoUrl> {
995 response
996 .headers
997 .get(header_name)
998 .and_then(|value| value.to_str().ok())
999 .and_then(|location| request.current_url().join(location).ok())
1000}
1001
1002impl HttpCache {
1003 pub(crate) async fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
1007 let entry_key = CacheKey::new(request);
1008
1009 let cached_resources = match self.entries.get(&entry_key) {
1010 None => return,
1011 Some(resources) => resources,
1012 };
1013
1014 let actual_response = response.actual_response();
1015
1016 let lock = cached_resources.read().await;
1019 let relevant_cached_resources = lock.iter().filter(|resource| {
1020 if actual_response.is_network_error() {
1021 return *resource.body.lock() == ResponseBody::Empty;
1022 }
1023 resource.status == actual_response.status
1024 });
1025
1026 for cached_resource in relevant_cached_resources {
1027 let mut awaiting_consumers = cached_resource.awaiting_body.lock();
1028 if awaiting_consumers.is_empty() {
1029 continue;
1030 }
1031 let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
1032 Data::Cancelled
1037 } else {
1038 match *cached_resource.body.lock() {
1039 ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
1040 ResponseBody::Receiving(_) => {
1041 continue;
1042 },
1043 }
1044 };
1045 for done_sender in awaiting_consumers.drain(..) {
1046 let _ = done_sender.send(to_send.clone());
1047 }
1048 }
1049 }
1050
1051 pub(crate) fn cache_entry_descriptors(&self) -> Vec<CacheEntryDescriptor> {
1053 self.entries
1054 .iter()
1055 .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string()))
1056 .collect()
1057 }
1058
1059 pub(crate) fn clear(&self) {
1061 self.entries.clear();
1062 if let Some(disk_cache) = &self.disk_cache {
1063 disk_cache.clear();
1064 }
1065 }
1066
1067 pub async fn store(&self, request: &Request, response: &Response) {
1069 let guard = self.get_or_guard(CacheKey::new(request)).await;
1070 guard.insert(request, response);
1071 }
1072
1073 pub async fn construct_response(
1075 &self,
1076 request: &Request,
1077 done_chan: &mut DoneChannel,
1078 ) -> Option<Response> {
1079 let entry = self.entries.get(&CacheKey::new(request))?;
1080 let cached_resources = entry.read().await;
1081 construct_response(request, done_chan, cached_resources.as_slice())
1082 .map(|cached| cached.response)
1083 }
1084
1085 #[cfg(feature = "test-util")]
1088 pub async fn construct_response_freshness(
1089 &self,
1090 request: &Request,
1091 done_chan: &mut DoneChannel,
1092 ) -> Option<ValidationStatus> {
1093 let entry = self.entries.get(&CacheKey::new(request))?;
1094 let cached_resources = entry.read().await;
1095 construct_response(request, done_chan, cached_resources.as_slice())
1096 .map(|cached| cached.validation_status)
1097 }
1098
1099 pub(crate) async fn invalidate_related_urls(
1101 &self,
1102 request: &Request,
1103 response: &Response,
1104 skip_key: &CacheKey,
1105 ) {
1106 for header_name in &[header::LOCATION, header::CONTENT_LOCATION] {
1107 if let Some(location_url) = resolve_location_url(request, response, header_name.clone())
1108 {
1109 let location_key = CacheKey::from_url(location_url);
1110 if &location_key != skip_key {
1111 self.invalidate_entry(&location_key).await;
1112 }
1113 }
1114 }
1115 }
1116
1117 async fn invalidate_entry(&self, key: &CacheKey) {
1118 if let Some(entry) = self.entries.get(key) {
1119 let mut guarded_resources = entry.write().await;
1120 invalidate_cached_resources(guarded_resources.as_mut_slice());
1121 }
1122 }
1123
1124 #[servo_tracing::instrument(skip(self))]
1127 pub async fn get_or_guard(&self, entry_key: CacheKey) -> CachedResourcesOrGuard<'_> {
1128 let guard_or_value = self.entries.get_value_or_guard_async(&entry_key).await;
1129 if let Ok(value) = guard_or_value {
1130 return CachedResourcesOrGuard::Value(value.write_owned().await);
1131 }
1132 let guard = guard_or_value.unwrap_err();
1133
1134 if let Some(disk_cache) = &self.disk_cache {
1135 if let Some(response) = disk_cache.get(entry_key).await {
1136 if guard.insert(response.clone()).is_err() {
1137 error!(
1138 "Cache guard is invalid. This should not happen. Cache will be in inconsistent state."
1139 );
1140 }
1141 let response = response.write_owned().await;
1142 CachedResourcesOrGuard::Value(response)
1143 } else {
1144 CachedResourcesOrGuard::Guard(guard)
1145 }
1146 } else {
1147 CachedResourcesOrGuard::Guard(guard)
1148 }
1149 }
1150}
1151
1152pub enum CachedResourcesOrGuard<'a> {
1155 Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
1157 Guard(QuickCachePlaceholderGuard<'a>),
1159}
1160
1161impl<'a> CachedResourcesOrGuard<'a> {
1162 pub fn insert(self, request: &Request, response: &Response) {
1164 if pref!(network_http_cache_disabled) {
1165 return;
1166 }
1167
1168 if request.method != Method::GET {
1169 return;
1171 }
1172 if request.headers.contains_key(header::AUTHORIZATION) {
1173 return;
1180 };
1181 let metadata = match response.metadata() {
1182 Ok(FetchMetadata::Filtered {
1183 filtered: _,
1184 unsafe_: metadata,
1185 }) |
1186 Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
1187 _ => return,
1188 };
1189 if !response_is_cacheable(&metadata) {
1190 return;
1191 }
1192 let expiry = get_response_expiry(response);
1193 let stale_while_revalidate = get_stale_while_revalidate(&response.headers);
1194 let cacheable_metadata = CachedMetadata {
1195 headers: Arc::new(ParkingLotMutex::new(response.headers.clone().into())),
1196 final_url: metadata.final_url,
1197 content_type: metadata.content_type.map(|v| v.0.to_string()),
1198 charset: metadata.charset,
1199 status: metadata.status,
1200 };
1201 let entry_resource = CachedResource {
1202 request_headers: Arc::new(ParkingLotMutex::new(request.headers.clone().into())),
1203 body: response.body.clone(),
1204 aborted: response.aborted.clone(),
1205 awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
1206 metadata: cacheable_metadata,
1207 location_url: response.location_url.clone(),
1208 status: response.status.clone(),
1209 url_list: response.url_list.clone(),
1210 expires: expiry,
1211 stale_while_revalidate,
1212 revalidating: StdArc::new(AtomicBool::new(false)),
1213 last_validated: SystemTime::now(),
1214 };
1215
1216 match self {
1217 CachedResourcesOrGuard::Value(mut owned_rw_lock_write_guard) => {
1218 owned_rw_lock_write_guard.push(entry_resource);
1219 },
1220 CachedResourcesOrGuard::Guard(placeholder_guard) => {
1221 if placeholder_guard
1222 .insert(std::sync::Arc::new(TokioRwLock::new(vec![entry_resource])))
1223 .is_err()
1224 {
1225 error!("Could not insert into cache");
1226 }
1227 },
1228 }
1229 }
1230
1231 pub fn try_as_mut(&mut self) -> Option<&mut Vec<CachedResource>> {
1233 match self {
1234 CachedResourcesOrGuard::Value(owned_rw_lock_write_guard) => {
1235 Some(owned_rw_lock_write_guard.as_mut())
1236 },
1237 CachedResourcesOrGuard::Guard(_) => None,
1238 }
1239 }
1240}