Skip to main content

pingora_cache/
meta.rs

1// Copyright 2026 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Metadata for caching
16
17pub use http::Extensions;
18use log::warn;
19use once_cell::sync::{Lazy, OnceCell};
20use pingora_error::{Error, ErrorType::*, OrErr, Result};
21use pingora_header_serde::HeaderSerde;
22use pingora_http::{HMap, ResponseHeader};
23use serde::{Deserialize, Serialize};
24use std::borrow::Cow;
25use std::time::{Duration, SystemTime};
26
27use crate::key::HashBinary;
28
29pub(crate) type InternalMeta = internal_meta::InternalMetaLatest;
30mod internal_meta {
31    use super::*;
32
33    pub(crate) type InternalMetaLatest = InternalMetaV2;
34
35    #[derive(Debug, Deserialize, Serialize, Clone)]
36    pub(crate) struct InternalMetaV0 {
37        pub(crate) fresh_until: SystemTime,
38        pub(crate) created: SystemTime,
39        pub(crate) stale_while_revalidate_sec: u32,
40        pub(crate) stale_if_error_sec: u32,
41        // Do not add more field
42    }
43
44    impl InternalMetaV0 {
45        #[allow(dead_code)]
46        fn serialize(&self) -> Result<Vec<u8>> {
47            rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
48        }
49
50        fn deserialize(buf: &[u8]) -> Result<Self> {
51            rmp_serde::decode::from_slice(buf)
52                .or_err(InternalError, "failed to decode cache meta v0")
53        }
54    }
55
56    #[derive(Debug, Deserialize, Serialize, Clone)]
57    pub(crate) struct InternalMetaV1 {
58        pub(crate) version: u8,
59        pub(crate) fresh_until: SystemTime,
60        pub(crate) created: SystemTime,
61        pub(crate) stale_while_revalidate_sec: u32,
62        pub(crate) stale_if_error_sec: u32,
63        // Do not add more field
64    }
65
66    impl InternalMetaV1 {
67        #[allow(dead_code)]
68        pub const VERSION: u8 = 1;
69
70        #[allow(dead_code)]
71        pub fn serialize(&self) -> Result<Vec<u8>> {
72            assert_eq!(self.version, 1);
73            rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
74        }
75
76        fn deserialize(buf: &[u8]) -> Result<Self> {
77            rmp_serde::decode::from_slice(buf)
78                .or_err(InternalError, "failed to decode cache meta v1")
79        }
80    }
81
82    #[derive(Debug, Deserialize, Serialize, Clone)]
83    pub(crate) struct InternalMetaV2 {
84        pub(crate) version: u8,
85        pub(crate) fresh_until: SystemTime,
86        pub(crate) created: SystemTime,
87        pub(crate) updated: SystemTime,
88        pub(crate) stale_while_revalidate_sec: u32,
89        pub(crate) stale_if_error_sec: u32,
90        // Only the extended field to be added below. One field at a time.
91        // 1. serde default in order to accept an older version schema without the field existing
92        // 2. serde skip_serializing_if in order for software with only an older version of this
93        //    schema to decode it
94        // After full releases, remove `skip_serializing_if` so that we can add the next extended field.
95        #[serde(default)]
96        pub(crate) variance: Option<HashBinary>,
97        #[serde(default)]
98        #[serde(skip_serializing_if = "Option::is_none")]
99        pub(crate) epoch_override: Option<SystemTime>,
100    }
101
102    impl Default for InternalMetaV2 {
103        fn default() -> Self {
104            let epoch = SystemTime::UNIX_EPOCH;
105            InternalMetaV2 {
106                version: InternalMetaV2::VERSION,
107                fresh_until: epoch,
108                created: epoch,
109                updated: epoch,
110                stale_while_revalidate_sec: 0,
111                stale_if_error_sec: 0,
112                variance: None,
113                epoch_override: None,
114            }
115        }
116    }
117
118    impl InternalMetaV2 {
119        pub const VERSION: u8 = 2;
120
121        pub fn serialize(&self) -> Result<Vec<u8>> {
122            assert_eq!(self.version, Self::VERSION);
123            rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
124        }
125
126        fn deserialize(buf: &[u8]) -> Result<Self> {
127            rmp_serde::decode::from_slice(buf)
128                .or_err(InternalError, "failed to decode cache meta v2")
129        }
130    }
131
132    impl From<InternalMetaV0> for InternalMetaV2 {
133        fn from(v0: InternalMetaV0) -> Self {
134            InternalMetaV2 {
135                version: InternalMetaV2::VERSION,
136                fresh_until: v0.fresh_until,
137                created: v0.created,
138                updated: v0.created,
139                stale_while_revalidate_sec: v0.stale_while_revalidate_sec,
140                stale_if_error_sec: v0.stale_if_error_sec,
141                ..Default::default()
142            }
143        }
144    }
145
146    impl From<InternalMetaV1> for InternalMetaV2 {
147        fn from(v1: InternalMetaV1) -> Self {
148            InternalMetaV2 {
149                version: InternalMetaV2::VERSION,
150                fresh_until: v1.fresh_until,
151                created: v1.created,
152                updated: v1.created,
153                stale_while_revalidate_sec: v1.stale_while_revalidate_sec,
154                stale_if_error_sec: v1.stale_if_error_sec,
155                ..Default::default()
156            }
157        }
158    }
159
160    // cross version decode
161    pub(crate) fn deserialize(buf: &[u8]) -> Result<InternalMetaLatest> {
162        const MIN_SIZE: usize = 10; // a small number to read the first few bytes
163        if buf.len() < MIN_SIZE {
164            return Error::e_explain(
165                InternalError,
166                format!("Buf too short ({}) to be InternalMeta", buf.len()),
167            );
168        }
169        let preread_buf = &mut &buf[..MIN_SIZE];
170        // the struct is always packed as a fixed size array
171        match rmp::decode::read_array_len(preread_buf)
172            .or_err(InternalError, "failed to decode cache meta array size")?
173        {
174            // v0 has 4 items and no version number
175            4 => Ok(InternalMetaV0::deserialize(buf)?.into()),
176            // other V should have version number encoded
177            _ => {
178                // rmp will encode `version` < 128 into a fixint (one byte),
179                // so we use read_pfix
180                let version = rmp::decode::read_pfix(preread_buf)
181                    .or_err(InternalError, "failed to decode meta version")?;
182                match version {
183                    1 => Ok(InternalMetaV1::deserialize(buf)?.into()),
184                    2 => InternalMetaV2::deserialize(buf),
185                    _ => Error::e_explain(
186                        InternalError,
187                        format!("Unknown InternalMeta version {version}"),
188                    ),
189                }
190            }
191        }
192    }
193
194    #[cfg(test)]
195    mod tests {
196        use super::*;
197
198        #[test]
199        fn test_internal_meta_serde_v0() {
200            let meta = InternalMetaV0 {
201                fresh_until: SystemTime::now(),
202                created: SystemTime::now(),
203                stale_while_revalidate_sec: 0,
204                stale_if_error_sec: 0,
205            };
206            let binary = meta.serialize().unwrap();
207            let meta2 = InternalMetaV0::deserialize(&binary).unwrap();
208            assert_eq!(meta.fresh_until, meta2.fresh_until);
209        }
210
211        #[test]
212        fn test_internal_meta_serde_v1() {
213            let meta = InternalMetaV1 {
214                version: InternalMetaV1::VERSION,
215                fresh_until: SystemTime::now(),
216                created: SystemTime::now(),
217                stale_while_revalidate_sec: 0,
218                stale_if_error_sec: 0,
219            };
220            let binary = meta.serialize().unwrap();
221            let meta2 = InternalMetaV1::deserialize(&binary).unwrap();
222            assert_eq!(meta.fresh_until, meta2.fresh_until);
223        }
224
225        #[test]
226        fn test_internal_meta_serde_v2() {
227            let meta = InternalMetaV2::default();
228            let binary = meta.serialize().unwrap();
229            let meta2 = InternalMetaV2::deserialize(&binary).unwrap();
230            assert_eq!(meta2.version, 2);
231            assert_eq!(meta.fresh_until, meta2.fresh_until);
232            assert_eq!(meta.created, meta2.created);
233            assert_eq!(meta.updated, meta2.updated);
234        }
235
236        #[test]
237        fn test_internal_meta_serde_across_versions() {
238            let meta = InternalMetaV0 {
239                fresh_until: SystemTime::now(),
240                created: SystemTime::now(),
241                stale_while_revalidate_sec: 0,
242                stale_if_error_sec: 0,
243            };
244            let binary = meta.serialize().unwrap();
245            let meta2 = deserialize(&binary).unwrap();
246            assert_eq!(meta2.version, 2);
247            assert_eq!(meta.fresh_until, meta2.fresh_until);
248
249            let meta = InternalMetaV1 {
250                version: 1,
251                fresh_until: SystemTime::now(),
252                created: SystemTime::now(),
253                stale_while_revalidate_sec: 0,
254                stale_if_error_sec: 0,
255            };
256            let binary = meta.serialize().unwrap();
257            let meta2 = deserialize(&binary).unwrap();
258            assert_eq!(meta2.version, 2);
259            assert_eq!(meta.fresh_until, meta2.fresh_until);
260            // `updated` == `created` when upgrading to v2
261            assert_eq!(meta2.created, meta2.updated);
262        }
263
264        // make sure that v2 format is backward compatible
265        // this is the base version of v2 without any extended fields
266        #[derive(Deserialize, Serialize)]
267        struct InternalMetaV2Base {
268            version: u8,
269            fresh_until: SystemTime,
270            created: SystemTime,
271            updated: SystemTime,
272            stale_while_revalidate_sec: u32,
273            stale_if_error_sec: u32,
274        }
275
276        impl InternalMetaV2Base {
277            pub const VERSION: u8 = 2;
278            pub fn serialize(&self) -> Result<Vec<u8>> {
279                assert!(self.version >= Self::VERSION);
280                rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
281            }
282            fn deserialize(buf: &[u8]) -> Result<Self> {
283                rmp_serde::decode::from_slice(buf)
284                    .or_err(InternalError, "failed to decode cache meta v2")
285            }
286        }
287
288        // this is the base version of v2 with variance but without epoch_override
289        #[derive(Deserialize, Serialize)]
290        struct InternalMetaV2BaseWithVariance {
291            version: u8,
292            fresh_until: SystemTime,
293            created: SystemTime,
294            updated: SystemTime,
295            stale_while_revalidate_sec: u32,
296            stale_if_error_sec: u32,
297            #[serde(default)]
298            #[serde(skip_serializing_if = "Option::is_none")]
299            variance: Option<HashBinary>,
300        }
301
302        impl Default for InternalMetaV2BaseWithVariance {
303            fn default() -> Self {
304                let epoch = SystemTime::UNIX_EPOCH;
305                InternalMetaV2BaseWithVariance {
306                    version: InternalMetaV2::VERSION,
307                    fresh_until: epoch,
308                    created: epoch,
309                    updated: epoch,
310                    stale_while_revalidate_sec: 0,
311                    stale_if_error_sec: 0,
312                    variance: None,
313                }
314            }
315        }
316
317        impl InternalMetaV2BaseWithVariance {
318            pub const VERSION: u8 = 2;
319            pub fn serialize(&self) -> Result<Vec<u8>> {
320                assert!(self.version >= Self::VERSION);
321                rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
322            }
323            fn deserialize(buf: &[u8]) -> Result<Self> {
324                rmp_serde::decode::from_slice(buf)
325                    .or_err(InternalError, "failed to decode cache meta v2")
326            }
327        }
328
329        #[test]
330        fn test_internal_meta_serde_v2_extend_fields_variance() {
331            // ext V2 to base v2
332            let meta = InternalMetaV2BaseWithVariance::default();
333            let binary = meta.serialize().unwrap();
334            let meta2 = InternalMetaV2Base::deserialize(&binary).unwrap();
335            assert_eq!(meta2.version, 2);
336            assert_eq!(meta.fresh_until, meta2.fresh_until);
337            assert_eq!(meta.created, meta2.created);
338            assert_eq!(meta.updated, meta2.updated);
339
340            // base V2 to ext v2
341            let now = SystemTime::now();
342            let meta = InternalMetaV2Base {
343                version: InternalMetaV2::VERSION,
344                fresh_until: now,
345                created: now,
346                updated: now,
347                stale_while_revalidate_sec: 0,
348                stale_if_error_sec: 0,
349            };
350            let binary = meta.serialize().unwrap();
351            let meta2 = InternalMetaV2BaseWithVariance::deserialize(&binary).unwrap();
352            assert_eq!(meta2.version, 2);
353            assert_eq!(meta.fresh_until, meta2.fresh_until);
354            assert_eq!(meta.created, meta2.created);
355            assert_eq!(meta.updated, meta2.updated);
356        }
357
358        #[test]
359        fn test_internal_meta_serde_v2_extend_fields_epoch_override() {
360            let now = SystemTime::now();
361
362            // ext V2 (with epoch_override = None) to V2 with variance (without epoch_override field)
363            let meta = InternalMetaV2 {
364                fresh_until: now,
365                created: now,
366                updated: now,
367                epoch_override: None, // None means it will be skipped during serialization
368                ..Default::default()
369            };
370            let binary = meta.serialize().unwrap();
371            let meta2 = InternalMetaV2BaseWithVariance::deserialize(&binary).unwrap();
372            assert_eq!(meta2.version, 2);
373            assert_eq!(meta.fresh_until, meta2.fresh_until);
374            assert_eq!(meta.created, meta2.created);
375            assert_eq!(meta.updated, meta2.updated);
376            assert!(meta2.variance.is_none());
377
378            // V2 base with variance (without epoch_override) to ext V2 (with epoch_override)
379            let mut meta = InternalMetaV2BaseWithVariance {
380                version: InternalMetaV2::VERSION,
381                fresh_until: now,
382                created: now,
383                updated: now,
384                stale_while_revalidate_sec: 0,
385                stale_if_error_sec: 0,
386                variance: None,
387            };
388            let binary = meta.serialize().unwrap();
389            let meta2 = InternalMetaV2::deserialize(&binary).unwrap();
390            assert_eq!(meta2.version, 2);
391            assert_eq!(meta.fresh_until, meta2.fresh_until);
392            assert_eq!(meta.created, meta2.created);
393            assert_eq!(meta.updated, meta2.updated);
394            assert!(meta2.variance.is_none());
395            assert!(meta2.epoch_override.is_none());
396
397            // try with variance set
398            meta.variance = Some(*b"variance_testing");
399            let binary = meta.serialize().unwrap();
400            let meta2 = InternalMetaV2::deserialize(&binary).unwrap();
401            assert_eq!(meta2.version, 2);
402            assert_eq!(meta.fresh_until, meta2.fresh_until);
403            assert_eq!(meta.created, meta2.created);
404            assert_eq!(meta.updated, meta2.updated);
405            assert_eq!(meta.variance, meta2.variance);
406            assert!(meta2.epoch_override.is_none());
407        }
408    }
409}
410
411#[derive(Debug)]
412pub(crate) struct CacheMetaInner {
413    // http header and Internal meta have different ways of serialization, so keep them separated
414    pub(crate) internal: InternalMeta,
415    pub(crate) header: ResponseHeader,
416    /// An opaque type map to hold extra information for communication between cache backends
417    /// and users. This field is **not** guaranteed be persistently stored in the cache backend.
418    pub extensions: Extensions,
419}
420
421/// The cacheable response header and cache metadata
422#[derive(Debug)]
423pub struct CacheMeta(pub(crate) Box<CacheMetaInner>);
424
425impl CacheMeta {
426    /// Create a [CacheMeta] from the given metadata and the response header
427    pub fn new(
428        fresh_until: SystemTime,
429        created: SystemTime,
430        stale_while_revalidate_sec: u32,
431        stale_if_error_sec: u32,
432        header: ResponseHeader,
433    ) -> CacheMeta {
434        CacheMeta(Box::new(CacheMetaInner {
435            internal: InternalMeta {
436                version: InternalMeta::VERSION,
437                fresh_until,
438                created,
439                updated: created, // created == updated for new meta
440                stale_while_revalidate_sec,
441                stale_if_error_sec,
442                ..Default::default()
443            },
444            header,
445            extensions: Extensions::new(),
446        }))
447    }
448
449    /// When the asset was created/admitted to cache
450    pub fn created(&self) -> SystemTime {
451        self.0.internal.created
452    }
453
454    /// The last time the asset was revalidated
455    ///
456    /// This value will be the same as [Self::created()] if no revalidation ever happens
457    pub fn updated(&self) -> SystemTime {
458        self.0.internal.updated
459    }
460
461    /// The reference point for cache age. This represents the "starting point" for `fresh_until`.
462    ///
463    /// This defaults to the `updated` timestamp but is overridden by the `epoch_override` field
464    /// if set.
465    pub fn epoch(&self) -> SystemTime {
466        self.0.internal.epoch_override.unwrap_or(self.updated())
467    }
468
469    /// Get the epoch override for this asset
470    pub fn epoch_override(&self) -> Option<SystemTime> {
471        self.0.internal.epoch_override
472    }
473
474    /// Set the epoch override for this asset
475    ///
476    /// When set, this will be used as the reference point for calculating age and freshness
477    /// instead of the updated time.
478    pub fn set_epoch_override(&mut self, epoch: SystemTime) {
479        self.0.internal.epoch_override = Some(epoch);
480    }
481
482    /// Remove the epoch override for this asset
483    pub fn remove_epoch_override(&mut self) {
484        self.0.internal.epoch_override = None;
485    }
486
487    /// Is the asset still valid
488    pub fn is_fresh(&self, time: SystemTime) -> bool {
489        // NOTE: HTTP cache time resolution is second
490        self.0.internal.fresh_until >= time
491    }
492
493    /// How long (in seconds) the asset should be fresh since its admission/revalidation
494    ///
495    /// This is essentially the max-age value (or its equivalence).
496    /// If an epoch override is set, it will be used as the reference point instead of the updated time.
497    pub fn fresh_sec(&self) -> u64 {
498        // swallow `duration_since` error, assets that are always stale have earlier `fresh_until` than `created`
499        // practically speaking we can always treat these as 0 ttl
500        // XXX: return Error if `fresh_until` is much earlier than expected?
501        let reference = self.epoch();
502        self.0
503            .internal
504            .fresh_until
505            .duration_since(reference)
506            .map_or(0, |duration| duration.as_secs())
507    }
508
509    /// Until when the asset is considered fresh
510    pub fn fresh_until(&self) -> SystemTime {
511        self.0.internal.fresh_until
512    }
513
514    /// How old the asset is since its admission/revalidation
515    ///
516    /// If an epoch override is set, it will be used as the reference point instead of the updated time.
517    pub fn age(&self) -> Duration {
518        let reference = self.epoch();
519        SystemTime::now()
520            .duration_since(reference)
521            .unwrap_or_default()
522    }
523
524    /// The stale-while-revalidate limit in seconds
525    pub fn stale_while_revalidate_sec(&self) -> u32 {
526        self.0.internal.stale_while_revalidate_sec
527    }
528
529    /// The stale-if-error limit in seconds
530    pub fn stale_if_error_sec(&self) -> u32 {
531        self.0.internal.stale_if_error_sec
532    }
533
534    /// Can the asset be used to serve stale during revalidation at the given time.
535    ///
536    /// NOTE: the serve stale functions do not check !is_fresh(time),
537    /// i.e. the object is already assumed to be stale.
538    pub fn serve_stale_while_revalidate(&self, time: SystemTime) -> bool {
539        self.can_serve_stale(self.0.internal.stale_while_revalidate_sec, time)
540    }
541
542    /// Can the asset be used to serve stale after error at the given time.
543    ///
544    /// NOTE: the serve stale functions do not check !is_fresh(time),
545    /// i.e. the object is already assumed to be stale.
546    pub fn serve_stale_if_error(&self, time: SystemTime) -> bool {
547        self.can_serve_stale(self.0.internal.stale_if_error_sec, time)
548    }
549
550    /// Disable serve stale for this asset
551    pub fn disable_serve_stale(&mut self) {
552        self.0.internal.stale_if_error_sec = 0;
553        self.0.internal.stale_while_revalidate_sec = 0;
554    }
555
556    /// Get the variance hash of this asset
557    pub fn variance(&self) -> Option<HashBinary> {
558        self.0.internal.variance
559    }
560
561    /// Set the variance key of this asset
562    pub fn set_variance_key(&mut self, variance_key: HashBinary) {
563        self.0.internal.variance = Some(variance_key);
564    }
565
566    /// Set the variance (hash) of this asset
567    pub fn set_variance(&mut self, variance: HashBinary) {
568        self.0.internal.variance = Some(variance)
569    }
570
571    /// Removes the variance (hash) of this asset
572    pub fn remove_variance(&mut self) {
573        self.0.internal.variance = None
574    }
575
576    /// Get the response header in this asset
577    pub fn response_header(&self) -> &ResponseHeader {
578        &self.0.header
579    }
580
581    /// Modify the header in this asset
582    pub fn response_header_mut(&mut self) -> &mut ResponseHeader {
583        &mut self.0.header
584    }
585
586    /// Expose the extensions to read
587    pub fn extensions(&self) -> &Extensions {
588        &self.0.extensions
589    }
590
591    /// Expose the extensions to modify
592    pub fn extensions_mut(&mut self) -> &mut Extensions {
593        &mut self.0.extensions
594    }
595
596    /// Get a copy of the response header
597    pub fn response_header_copy(&self) -> ResponseHeader {
598        self.0.header.clone()
599    }
600
601    /// get all the headers of this asset
602    pub fn headers(&self) -> &HMap {
603        &self.0.header.headers
604    }
605
606    fn can_serve_stale(&self, serve_stale_sec: u32, time: SystemTime) -> bool {
607        if serve_stale_sec == 0 {
608            return false;
609        }
610        if let Some(stale_until) = self
611            .0
612            .internal
613            .fresh_until
614            .checked_add(Duration::from_secs(serve_stale_sec.into()))
615        {
616            stale_until >= time
617        } else {
618            // overflowed: treat as infinite ttl
619            true
620        }
621    }
622
623    /// Serialize this object
624    pub fn serialize(&self) -> Result<(Vec<u8>, Vec<u8>)> {
625        let internal = self.0.internal.serialize()?;
626        let header = header_serialize(&self.0.header)?;
627        log::debug!("header to serialize: {:?}", &self.0.header);
628        Ok((internal, header))
629    }
630
631    /// Deserialize from the binary format
632    pub fn deserialize(internal: &[u8], header: &[u8]) -> Result<Self> {
633        let internal = internal_meta::deserialize(internal)?;
634        let header = header_deserialize(header)?;
635        Ok(CacheMeta(Box::new(CacheMetaInner {
636            internal,
637            header,
638            extensions: Extensions::new(),
639        })))
640    }
641}
642
643use http::StatusCode;
644
645/// The function to generate TTL from the given [StatusCode].
646pub type FreshDurationByStatusFn = fn(StatusCode) -> Option<Duration>;
647
648/// The default settings to generate [CacheMeta]
649pub struct CacheMetaDefaults {
650    // if a status code is not included in fresh_sec, it's not considered cacheable by default.
651    fresh_sec_fn: FreshDurationByStatusFn,
652    stale_while_revalidate_sec: u32,
653    // TODO: allow "error" condition to be configurable?
654    stale_if_error_sec: u32,
655}
656
657impl CacheMetaDefaults {
658    /// Create a new [CacheMetaDefaults]
659    pub const fn new(
660        fresh_sec_fn: FreshDurationByStatusFn,
661        stale_while_revalidate_sec: u32,
662        stale_if_error_sec: u32,
663    ) -> Self {
664        CacheMetaDefaults {
665            fresh_sec_fn,
666            stale_while_revalidate_sec,
667            stale_if_error_sec,
668        }
669    }
670
671    /// Return the default TTL for the given [StatusCode]
672    ///
673    /// `None`: do no cache this code.
674    pub fn fresh_sec(&self, resp_status: StatusCode) -> Option<Duration> {
675        // safe guard to make sure 304 response to share the same default ttl of 200
676        if resp_status == StatusCode::NOT_MODIFIED {
677            (self.fresh_sec_fn)(StatusCode::OK)
678        } else {
679            (self.fresh_sec_fn)(resp_status)
680        }
681    }
682
683    /// The default SWR seconds
684    pub fn serve_stale_while_revalidate_sec(&self) -> u32 {
685        self.stale_while_revalidate_sec
686    }
687
688    /// The default SIE seconds
689    pub fn serve_stale_if_error_sec(&self) -> u32 {
690        self.stale_if_error_sec
691    }
692}
693
694/// The dictionary content for header compression.
695///
696/// Used during initialization of [`HEADER_SERDE`].
697static COMPRESSION_DICT_CONTENT: OnceCell<Cow<'static, [u8]>> = OnceCell::new();
698
699static HEADER_SERDE: Lazy<HeaderSerde> = Lazy::new(|| {
700    let dict_opt = if let Some(dict_content) = COMPRESSION_DICT_CONTENT.get() {
701        Some(dict_content.to_vec())
702    } else {
703        warn!("no header compression dictionary loaded - use set_compression_dict_content() or set_compression_dict_path() to set one");
704        None
705    };
706
707    HeaderSerde::new(dict_opt)
708});
709
710pub(crate) fn header_serialize(header: &ResponseHeader) -> Result<Vec<u8>> {
711    HEADER_SERDE.serialize(header)
712}
713
714pub(crate) fn header_deserialize<T: AsRef<[u8]>>(buf: T) -> Result<ResponseHeader> {
715    HEADER_SERDE.deserialize(buf.as_ref())
716}
717
718/// Load the header compression dictionary from a file, which helps serialize http header.
719///
720/// Returns false if it is already set or if the file could not be read.
721///
722/// Use [`set_compression_dict_content`] to set the dictionary from memory instead.
723pub fn set_compression_dict_path(path: &str) -> bool {
724    match std::fs::read(path) {
725        Ok(dict) => COMPRESSION_DICT_CONTENT.set(dict.into()).is_ok(),
726        Err(e) => {
727            warn!(
728                "failed to read header compress dictionary file at {}, {:?}",
729                path, e
730            );
731            false
732        }
733    }
734}
735
736/// Set the header compression dictionary content, which helps serialize http header.
737///
738/// Returns false if it is already set.
739///
740/// This is an alernative to [`set_compression_dict_path`], allowing use of
741/// a dictionary without an external file.
742pub fn set_compression_dict_content(content: Cow<'static, [u8]>) -> bool {
743    COMPRESSION_DICT_CONTENT.set(content).is_ok()
744}
745
746#[cfg(test)]
747mod tests {
748    use super::*;
749    use std::time::Duration;
750
751    #[test]
752    fn test_cache_meta_age_without_override() {
753        let now = SystemTime::now();
754        let header = ResponseHeader::build_no_case(200, None).unwrap();
755        let meta = CacheMeta::new(now + Duration::from_secs(300), now, 0, 0, header);
756
757        // Without epoch_override, age() should use updated() as reference
758        std::thread::sleep(Duration::from_millis(100));
759        let age = meta.age();
760        assert!(age.as_secs() < 1, "age should be close to 0");
761
762        // epoch() should return updated() when no override is set
763        assert_eq!(meta.epoch(), meta.updated());
764    }
765
766    #[test]
767    fn test_cache_meta_age_with_epoch_override_past() {
768        let now = SystemTime::now();
769        let header = ResponseHeader::build(200, None).unwrap();
770        let mut meta = CacheMeta::new(now + Duration::from_secs(300), now, 0, 0, header);
771
772        // Set epoch_override to 10 seconds in the past
773        let epoch_override = now - Duration::from_secs(10);
774        meta.set_epoch_override(epoch_override);
775
776        // age() should now use epoch_override as the reference
777        let age = meta.age();
778        assert!(age.as_secs() >= 10);
779        assert!(age.as_secs() < 12);
780
781        // epoch() should return the override
782        assert_eq!(meta.epoch(), epoch_override);
783        assert_eq!(meta.epoch_override(), Some(epoch_override));
784    }
785
786    #[test]
787    fn test_cache_meta_age_with_epoch_override_future() {
788        let now = SystemTime::now();
789        let header = ResponseHeader::build(200, None).unwrap();
790        let mut meta = CacheMeta::new(now + Duration::from_secs(100), now, 0, 0, header);
791
792        // Set epoch_override to a future time
793        let future_epoch = now + Duration::from_secs(10);
794        meta.set_epoch_override(future_epoch);
795
796        let age_with_epoch = meta.age();
797        // age should be 0 since epoch_override is in the future
798        assert_eq!(age_with_epoch, Duration::ZERO);
799    }
800
801    #[test]
802    fn test_cache_meta_fresh_sec() {
803        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
804        let mut meta = CacheMeta::new(
805            SystemTime::now() + Duration::from_secs(100),
806            SystemTime::now() - Duration::from_secs(100),
807            0,
808            0,
809            header,
810        );
811
812        meta.0.internal.updated = SystemTime::UNIX_EPOCH + Duration::from_secs(1000);
813        meta.0.internal.fresh_until = SystemTime::UNIX_EPOCH + Duration::from_secs(1100);
814
815        // Without epoch_override, fresh_sec should use updated as reference
816        let fresh_sec_without_override = meta.fresh_sec();
817        assert_eq!(fresh_sec_without_override, 100); // 1100 - 1000 = 100 seconds
818
819        // With epoch_override set to a later time (1050), fresh_sec should be calculated from that reference
820        let epoch_override = SystemTime::UNIX_EPOCH + Duration::from_secs(1050);
821        meta.set_epoch_override(epoch_override);
822        assert_eq!(meta.epoch_override(), Some(epoch_override));
823        assert_eq!(meta.epoch(), epoch_override);
824
825        let fresh_sec_with_override = meta.fresh_sec();
826        // fresh_until - epoch_override = 1100 - 1050 = 50 seconds
827        assert_eq!(fresh_sec_with_override, 50);
828
829        meta.remove_epoch_override();
830        assert_eq!(meta.epoch_override(), None);
831        assert_eq!(meta.epoch(), meta.updated());
832        assert_eq!(meta.fresh_sec(), 100); // back to normal calculation
833    }
834}