viceroy_lib/cache/
variance.rs

1//! Support for request- and response-keyed variance, per HTTP's vary rules
2//!
3//! HTTP caching as described in RFC 9111 has two components to a key. The first is the "request
4//! key", defined by the caching entity -- typically consisting of the URL and often the method.
5//! The response from the server may include a Vary header, which lists request field names
6//! (i.e. header names) that affect the cacheability of the response. A subsequent request must
7//! match all the Vary values in order to use the cached result.
8//!
9//! The core cache API provides the bones of this.
10//!
11
12use std::{collections::HashSet, str::FromStr};
13
14use bytes::{Bytes, BytesMut};
15pub use http::HeaderName;
16use http::{header::InvalidHeaderName, HeaderMap};
17
18use crate::Error;
19
20/// A rule for variance of a request.
21///
22/// This rule describes what fields (headers) are used to determine whether a new request "matches"
23/// a previous response.
24///
25/// VaryRule is canonicalized, with lowercase-named header names in sorted order.
26#[derive(Debug, Clone, PartialEq, Eq, Default)]
27pub struct VaryRule {
28    headers: Vec<HeaderName>,
29}
30
31impl FromStr for VaryRule {
32    type Err = Error;
33
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        let headers: Result<Vec<HeaderName>, InvalidHeaderName> =
36            s.split(" ").map(HeaderName::try_from).collect();
37        Ok(VaryRule::new(headers?.iter()))
38    }
39}
40
41impl VaryRule {
42    pub fn new<'a>(headers: impl IntoIterator<Item = &'a HeaderName>) -> VaryRule {
43        // Deduplicate:
44        let headers: HashSet<HeaderName> = headers.into_iter().cloned().collect();
45        let mut headers: Vec<HeaderName> = headers.into_iter().collect();
46        headers.sort_by(|a, b| a.as_str().cmp(b.as_str()));
47        VaryRule { headers }
48    }
49
50    /// Construct the Variant for the given headers: the (header, value) pairs that must be present
51    /// for a request to match a response.
52    pub fn variant(&self, headers: &HeaderMap) -> Variant {
53        let mut buf = BytesMut::new();
54        // Include the count, to avoid confusion from values that might contain our marker phrases.
55        buf.extend_from_slice(format!("[headers: {}]", self.headers.len()).as_bytes());
56
57        for header in self.headers.iter() {
58            buf.extend_from_slice(format!("[header: {}]", header.as_str().len()).as_bytes());
59            buf.extend_from_slice(header.as_str().as_bytes());
60
61            let values = headers.get_all(header);
62            buf.extend_from_slice(format!("[values: {}]", values.iter().count()).as_bytes());
63
64            for value in values.iter() {
65                buf.extend_from_slice(format!("[value: {}]", value.as_bytes().len()).as_bytes());
66                buf.extend_from_slice(value.as_bytes());
67            }
68        }
69        Variant {
70            signature: buf.into(),
71        }
72    }
73}
74
75/// The portion of a cache key that is defined by request and response.
76///
77/// A `vary_by` directive indicates that a cached object should only be matched if the headers
78/// listed in `vary_by` match that of the request that generated the cached object.
79#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Clone)]
80pub struct Variant {
81    /// The internal representation is an HTTP header block: headers and values separated by a CRLF
82    /// sequence. However, since header values may contain arbitrary bytes, this is a Bytes rather
83    /// than a String.
84    signature: Bytes,
85}
86
87#[cfg(test)]
88mod tests {
89    use super::VaryRule;
90
91    #[test]
92    fn vary_rule_uniqe_sorted() {
93        let vary1: VaryRule = "unknown-header Accept content-type".parse().unwrap();
94        let vary2: VaryRule = "content-type unknown-header unknown-header Accept"
95            .parse()
96            .unwrap();
97        assert_eq!(vary1, vary2);
98    }
99}