Skip to main content

net/
http_cache.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![deny(missing_docs)]
6
7//! A memory cache implementing the logic specified in <http://tools.ietf.org/html/rfc7234>
8//! and <http://tools.ietf.org/html/rfc7232>.
9
10use 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/// A duration in seconds.
42///
43/// This is the same as std::time::Duration except that we do not care about nanosecond precision.
44#[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/// The key used to differentiate requests in the cache.
108#[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    /// Create a cache-key from a request.
121    pub fn new(request: &Request) -> CacheKey {
122        CacheKey {
123            url: request.current_url(),
124        }
125    }
126
127    /// Create a cache-key from a resolved URL.
128    pub fn from_url(url: ServoUrl) -> CacheKey {
129        CacheKey { url }
130    }
131}
132
133/// A complete cached resource.
134#[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    /// Whether this [`CachedResource`] can be used to answer the given `Request`. This
163    /// is true if:
164    ///
165    /// 1. The resource is not for an aborted load.
166    /// 2. The fields named in the `Vary` header match those of the `Request`.
167    /// 3. It is not a partial (206) response or the request has a `Range` header. When the request
168    ///    has a `Range` header the caller is expected to decide which part of the resource to use.
169    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        // From <https://httpwg.org/specs/rfc9111.html#incomplete.responses>
181        // > A cache MAY complete a stored incomplete response by making a subsequent range
182        // > request (Section 14.2 of [HTTP]) and combining the successful response with the
183        // > stored response, as defined in Section 3.4. A cache MUST NOT use an incomplete
184        // > response to answer requests unless the response has been made complete, or the
185        // > request is partial and specifies a range wholly within the incomplete response.
186        // > A cache MUST NOT send a partial response to a client without explicitly marking
187        // > it using the 206 (Partial Content) status code.
188        //
189        // TODO: Combining partial content to fulfill a non-Range request
190        // See <https://httpwg.org/specs/rfc9111.html#combining.responses>.
191        self.status
192            .try_code()
193            .is_some_and(|code| code != StatusCode::PARTIAL_CONTENT)
194    }
195
196    /// Whether the given request headers match the cached resource's request headers
197    /// on every field named in the `Vary` header according to the rules specified in
198    /// <https://httpwg.org/specs/rfc9111.html#caching.negotiated.responses>.
199    ///
200    /// > When a cache receives a request that can be satisfied by a stored response and
201    /// > that stored response contains a Vary header field (Section 12.5.5 of [HTTP]), the
202    /// > cache MUST NOT use that stored response without revalidation unless all the
203    /// > presented request header fields nominated by that Vary field value match those
204    /// > fields in the original request (i.e., the request that caused the cached response
205    /// > to be stored).
206    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)]
230/// Wrapper type for HeaderMap
231struct 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/// Metadata about a loaded resource, such as is obtained from HTTP headers.
260#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
261struct CachedMetadata {
262    /// Headers
263    #[conditional_malloc_size_of]
264    pub headers: Arc<ParkingLotMutex<SerializeableHeaderMap>>,
265    /// Final URL after redirects.
266    pub final_url: ServoUrl,
267    /// MIME type / subtype.
268    pub content_type: Option<Serde<ContentType>>,
269    /// Character set.
270    pub charset: Option<String>,
271    /// HTTP Status
272    pub status: HttpStatus,
273}
274
275/// Whether a cached response is fresh or requires validation before or after use.
276#[derive(Clone, Copy, Debug, Eq, PartialEq)]
277pub enum ValidationStatus {
278    /// The response is fresh and can be used without any revalidation.
279    Valid,
280    /// The response is stale.
281    Stale {
282        /// Whether the stale response can be served immediately, leaving the
283        /// caller responsible for revalidating it in the background.
284        revalidate_in_background: bool,
285    },
286}
287
288/// Wrapper around a cached response, including information on re-validation needs
289pub(crate) struct CachedResponse {
290    /// The response constructed from the cached resource
291    pub response: Response,
292    /// Whether the stored response is fresh or stale
293    pub validation_status: ValidationStatus,
294    /// Single-flight guard for the background revalidation.
295    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/// Is this state assigned to the private or public side.
311#[derive(Debug, MallocSizeOf, PartialEq)]
312pub enum HttpCacheAssignment {
313    /// Public Cache State, possibly stored to disk.
314    Public,
315    /// Private Cache State, not stored to disk.
316    Private,
317}
318
319/// A simple memory cache.
320/// Elements will be evicted based on the cache heuristic. We weight elements
321/// by the number of entries per given url. We evict currently a whole url.
322/// The cache makes extensive use of `Arc::unwrap_or_clone or` and `Arc::into_inner`
323/// to modify the cached entries. This is ok because `CachedResource` are cheap to clone
324pub struct HttpCache {
325    /// cached responses.
326    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    /// Create a new HttpCache with [`HttpCacheAssignment`]
351    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    /// The number of entries in the memory cache. This does not say anything about the disk cache.
372    pub fn len(&self) -> usize {
373        self.entries.len()
374    }
375}
376
377#[derive(Clone)]
378/// The lifecycle hooks of the HttpCache.
379/// Responsible for moving data to the disk.
380pub 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    // Cached Resources that are not complete could get evicted which means they cannot fill their body.
394    // We allow unfinished resources to stay in the cache.
395    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
413/// Determine if a response is cacheable by default <https://tools.ietf.org/html/rfc7231#section-6.1>
414fn 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
421/// Determine if a given response is cacheable.
422/// Based on <https://tools.ietf.org/html/rfc7234#section-3>
423fn response_is_cacheable(metadata: &Metadata) -> bool {
424    // This kind of response does not have a body. It just freshens existing cached resources.
425    if metadata.status == StatusCode::NOT_MODIFIED {
426        return false;
427    }
428
429    // Resources with `Vary: '*'` can never be returned as cache results, so
430    // do not store them in the cache.
431    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    // TODO: if we determine that this cache should be considered shared:
440    // 1. check for absence of private response directive <https://tools.ietf.org/html/rfc7234#section-5.2.2.6>
441    // 2. check for absence of the Authorization header field.
442    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            // If cache-control is understood, we can use it and ignore pragma.
459            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
471/// Calculating Age
472/// <https://tools.ietf.org/html/rfc7234#section-4.2.3>
473fn calculate_response_age(response: &Response) -> ApproxDuration {
474    // TODO: follow the spec more closely (Date headers, request/response lag, ...)
475    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
484/// Determine the expiry date from relevant headers,
485/// or uses a heuristic if none are present.
486fn get_response_expiry(response: &Response) -> ApproxDuration {
487    // Calculating Freshness Lifetime <https://tools.ietf.org/html/rfc7234#section-4.2.1>
488    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            // Requires validation on first use.
493            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            // `duration_since` fails if `now` is later than `expiry_time` in which case,
503            // this whole thing return `Duration::ZERO`.
504            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        // Malformed Expires header, shouldn't be used to construct a valid response.
511        None if response.headers.contains_key(header::EXPIRES) => return ApproxDuration::zero(),
512        _ => {},
513    }
514    // Calculating Heuristic Freshness
515    // <https://tools.ietf.org/html/rfc7234#section-4.2.2>
516    if let Some(ref code) = response.status.try_code() {
517        // <https://tools.ietf.org/html/rfc7234#section-5.5.4>
518        // Since presently we do not generate a Warning header field with a 113 warn-code,
519        // 24 hours minus response age is the max for heuristic calculation.
520        let max_heuristic = ApproxDuration::from_secs(24 * 60 * 60).saturating_sub(age);
521        let heuristic_freshness = if let Some(last_modified) =
522            // If the response has a Last-Modified header field,
523            // caches are encouraged to use a heuristic expiration value
524            // that is no more than some fraction of the interval since that time.
525            response.headers.typed_get::<LastModified>()
526        {
527            // `time_since_last_modified` will be `Duration::ZERO` if `last_modified` is
528            // after `now`.
529            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            // A typical setting of this fraction might be 10%.
534            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            // Compatible with other browsers.
542            ApproxDuration::zero()
543        };
544        if is_cacheable_by_default(*code) {
545            // Status codes that are cacheable by default can use heuristics to determine freshness.
546            return heuristic_freshness;
547        }
548        // Other status codes can only use heuristic freshness if the public cache directive is present.
549        if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
550            directives.public()
551        {
552            return heuristic_freshness;
553        }
554    }
555    // Requires validation upon first use as default.
556    ApproxDuration::zero()
557}
558
559/// The `headers` crate's `CacheControl` does not understand `stale-while-revalidate` directive,
560/// so we need to parse the raw `Cache-Control` header values.
561/// <https://datatracker.ietf.org/doc/html/rfc5861#section-3>
562fn 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            // The argument is a number of seconds, optionally quoted.
576            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
585/// Determine whether the request itself demands revalidation.
586/// <https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1>
587fn 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        // The request's `no-store` directive is deliberately *not* treated as
596        // demanding revalidation: https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.5
597        // > "does not apply to the already stored response".
598        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
609/// Request Cache-Control Directives
610/// <https://tools.ietf.org/html/rfc7234#section-5.2.1>
611fn 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
646/// Create a CachedResponse from a request and a CachedResource.
647fn 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    // TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
685    // TODO: if this cache is to be considered shared, take proxy-revalidate into account
686    // <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
687    let has_expired = adjusted_expires <= time_since_validated;
688
689    // - fresh: return immediately, no validation.
690    // - stale:
691    //    - within the stale-while-revalidate window: return immediately + revalidate in the background
692    //    - beyond the stale-while-revalidate window: synchronous validation is required.
693    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
713/// Create a new resource, based on the bytes requested, and an existing resource,
714/// with a status-code of 206.
715fn 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
735/// Support for range requests <https://tools.ietf.org/html/rfc7233>.
736fn 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        // TODO: take the full range spec into account.
750        // If we have a complete resource, take the request range from the body.
751        // When there isn't a complete resource available, we loop over cached partials,
752        // and see if any individual partial response can fulfill the current request for a bytes range.
753        // TODO: combine partials that in combination could satisfy the requested range?
754        // see <https://tools.ietf.org/html/rfc7233#section-4.3>.
755        // TODO: add support for complete and partial resources,
756        // whose body is in the ResponseBody::Receiving state.
757        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                        // Prevent overflow on the addition below.
770                        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                        // Prevent overflow in the below operations from occuring.
868                        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                                // Requested range goes beyond the available range.
882                                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
903/// Constructing Responses from Caches.
904/// <https://tools.ietf.org/html/rfc7234#section-4>
905pub(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    // TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5>
915    debug!("trying to construct cache response for {:?}", request.url());
916    if request.method != Method::GET {
917        // Only Get requests are cached, avoid a url based match for others.
918        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    // Support for range requests
928    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        // Returning a response that can be constructed
934        // TODO: select the most appropriate one, using a known mechanism from a selecting header field,
935        // or using the Date header to return the most recent one.
936        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    // The cache wasn't able to construct anything.
945    None
946}
947
948/// Freshening Stored Responses upon Validation.
949/// <https://httpwg.org/specs/rfc9111.html#freshening.responses>
950pub 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    // Refresh the last cached resource that can be used to answer the request.
959    // TODO: This should follow the rules specified in
960    // <https://httpwg.org/specs/rfc9111.html#freshening.responses> more closely.
961    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        // done_chan will have been set to Some(..) by http_network_fetch.
974        // If the body is not receiving data, set the done_chan back to None.
975        // Otherwise, create a new dedicated channel to update the consumer.
976        // The response constructed here will replace the 304 one from the network.
977        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        // Received a response with 304 status code, in response to a request that matches a cached resource.
990        // 1. update the headers of the cached resource.
991        // 2. return a response, constructed from the cached resource.
992        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    // Update cached Resource with response and constructed response.
1013    if let Some(constructed_response) = constructed_response.as_mut() {
1014        let cached_resource = &mut cached_resources[cached_resource_index];
1015
1016        // Bracket is to minimize lock duration.
1017        {
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    /// Wake-up consumers of cached resources
1051    /// whose response body was still receiving data when the resource was constructed,
1052    /// and whose response has now either been completed or cancelled.
1053    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        // Ensure we only wake-up consumers of relevant resources,
1064        // ie we don't want to wake-up 200 awaiting consumers with a 206.
1065        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                // In the case of an aborted fetch,
1080                // wake-up all awaiting consumers.
1081                // Each will then start a new network request.
1082                // TODO: Wake-up only one consumer, and make it the producer on which others wait.
1083                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    /// Returns descriptors for cache entries currently stored in this cache.
1099    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    /// Clear the contents of this cache.
1107    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    /// Insert a response for `request` into the cache (used by tests that need direct access).
1115    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    /// Try to construct a cached response for `request`.
1121    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    /// Like [`construct_response`](Self::construct_response), but additionally
1133    /// reports the [`ValidationStatus`] of the constructed response.
1134    #[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    /// Invalidate cache entries referenced by Location/Content-Location headers.
1147    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    /// If the value exist in the cache, return it. If the value does not exist, return a guard you can use to insert values in the cache.
1172    /// If the guard is alive, all other accesses to this function will block.
1173    #[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
1199/// Returns an writeable entry into the cache or a guard for insertint an entry
1200/// The guard will block other queries to the cache entry in both cases.
1201pub enum CachedResourcesOrGuard<'a> {
1202    /// The value of the resource in the cache.
1203    Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
1204    /// A guard that blocks requests to the cache entry this guard is for.
1205    Guard(QuickCachePlaceholderGuard<'a>),
1206}
1207
1208impl<'a> CachedResourcesOrGuard<'a> {
1209    /// Insert into the cache according to http spec
1210    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            // Only Get requests are cached.
1217            return;
1218        }
1219        if request.headers.contains_key(header::AUTHORIZATION) {
1220            // https://tools.ietf.org/html/rfc7234#section-3.1
1221            // A shared cache MUST NOT use a cached response
1222            // to a request with an Authorization header field
1223            //
1224            // TODO: unless a cache directive that allows such
1225            // responses to be stored is present in the response.
1226            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                // Ensure that any existing complete response is replaced by the new
1261                // complete response if the two responses match according to the fields
1262                // named in the `Vary` header.
1263                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    /// If the guard is a value, return it as a mut reference. If the guard is a guard, return None
1285    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}