This commit is contained in:
Mateusz Gruszczyński committed 2026-10-04 11:15:58 +02:00
1 parent 87e571c170
commit fa7abc9e53
308 files changed
+49746 -50237

No files matched your search

+232
View File
@@ -0,0 +1,232 @@
fn parse_key_hex(value: &str) -> Option<Key> {
if value.len() != 64 || !value.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let mut key = [0_u8; 32];
for (index, chunk) in value.as_bytes().chunks_exact(2).enumerate() {
let text = std::str::from_utf8(chunk).ok()?;
key[index] = u8::from_str_radix(text, 16).ok()?;
}
Some(key)
}
fn default_cache_scheme() -> String {
"http".to_owned()
}
fn cache_tags(headers: &HeaderMap) -> Vec<String> {
let mut tags = Vec::new();
for value in headers.get_all("cache-tag") {
let Ok(value) = value.to_str() else { continue; };
for raw in value.split(|c: char| c == ',' || c.is_ascii_whitespace()) {
let tag = raw.trim();
if tag.is_empty()
|| tag.len() > 128
|| !tag
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.' | b':' | b'/'))
{
continue;
}
if !tags.iter().any(|existing| existing == tag) {
tags.push(tag.to_owned());
if tags.len() >= 64 {
return tags;
}
}
}
}
tags
}
fn now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
.min(u64::MAX as u128) as u64
}
fn system_time_ms(value: u64) -> SystemTime {
UNIX_EPOCH
.checked_add(Duration::from_millis(value))
.unwrap_or(UNIX_EPOCH)
}
fn cold_age(entry: &ColdEntry) -> u64 {
entry
.age_at_store_seconds
.saturating_add(entry.stored_at.elapsed().unwrap_or_default().as_secs())
}
fn cold_resident_age(entry: &ColdEntry) -> u64 {
entry
.resident_age_at_store_seconds
.saturating_add(entry.stored_at.elapsed().unwrap_or_default().as_secs())
}
fn retention_deadline(
_ttl: u64,
_stale_while_revalidate: u64,
_stale_if_error: u64,
_has_validator: bool,
) -> u64 {
// Freshness and storage retention are deliberately separate. Expiry only
// changes whether an object can be served without validation; it must not
// physically evict the representation because a later request may carry
// Cache-Control: max-stale. Capacity/LRU and Cold Cache GC remain the
// mechanisms that decide how long an otherwise valid object is retained.
u64::MAX
}
fn has_validator(headers: &HeaderMap) -> bool {
headers.contains_key("etag") || headers.contains_key("last-modified")
}
fn stale_forbidden(must_revalidate: bool, proxy_revalidate: bool) -> bool {
must_revalidate || proxy_revalidate
}
fn client_max_stale(headers: &HeaderMap) -> Option<Option<u64>> {
let control = directives(headers)?;
let value = control.get("max-stale")?;
match value {
None => Some(None),
Some(value) => value.parse::<u64>().ok().map(Some),
}
}
fn request_freshness_allows(age: u64, ttl: u64, headers: &HeaderMap) -> bool {
let Some(control) = directives(headers) else {
return false;
};
if let Some(Some(value)) = control.get("max-age") {
let Ok(limit) = value.parse::<u64>() else {
return false;
};
if age > limit {
return false;
}
}
if let Some(Some(value)) = control.get("min-fresh") {
let Ok(required) = value.parse::<u64>() else {
return false;
};
if ttl.saturating_sub(age) < required {
return false;
}
}
true
}
fn stale_mode(
age: u64,
ttl: u64,
stale_while_revalidate: u64,
must_revalidate: bool,
proxy_revalidate: bool,
requires_revalidation: bool,
request_headers: &HeaderMap,
) -> Option<bool> {
if age < ttl || requires_revalidation || stale_forbidden(must_revalidate, proxy_revalidate) {
return None;
}
if !request_freshness_allows(age, ttl, request_headers) {
return None;
}
let stale_for = age.saturating_sub(ttl);
if stale_while_revalidate > 0 && stale_for <= stale_while_revalidate {
return Some(true);
}
match client_max_stale(request_headers) {
Some(None) => Some(false),
Some(Some(limit)) if stale_for <= limit => Some(false),
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CacheState {
Fresh,
StaleButServable { background_revalidate: bool },
Expired,
}
impl CacheState {
fn servable(self) -> bool {
!matches!(self, Self::Expired)
}
fn stale(self) -> bool {
matches!(self, Self::StaleButServable { .. })
}
fn background_revalidate(self) -> bool {
matches!(
self,
Self::StaleButServable {
background_revalidate: true
}
)
}
}
fn cache_state(
age: u64,
ttl: u64,
stale_while_revalidate: u64,
must_revalidate: bool,
proxy_revalidate: bool,
requires_revalidation: bool,
request_headers: &HeaderMap,
) -> CacheState {
if age < ttl
&& !requires_revalidation
&& request_freshness_allows(age, ttl, request_headers)
{
return CacheState::Fresh;
}
match stale_mode(
age,
ttl,
stale_while_revalidate,
must_revalidate,
proxy_revalidate,
requires_revalidation,
request_headers,
) {
Some(background_revalidate) => CacheState::StaleButServable {
background_revalidate,
},
None => CacheState::Expired,
}
}
fn cold_paths(root: &Path, key: &Key) -> (PathBuf, PathBuf, PathBuf, PathBuf) {
let key = key_hex(key);
let shard = root.join(&key[..2]);
(
shard.join(format!("{key}.body")),
shard.join(format!("{key}.json")),
shard.join(format!(".{key}.body.tmp")),
shard.join(format!(".{key}.json.tmp")),
)
}
fn cold_header_pairs(headers: &HeaderMap) -> Option<Vec<(String, String)>> {
let mut result = Vec::new();
for (name, value) in headers {
result.push((name.as_str().to_owned(), value.to_str().ok()?.to_owned()));
}
Some(result)
}
fn pairs_to_headers(values: &[(String, String)]) -> Option<HeaderMap> {
let mut headers = HeaderMap::new();
for (name, value) in values {
let name = name.parse::<http::HeaderName>().ok()?;
let value = HeaderValue::from_str(value).ok()?;
headers.append(name, value);
}
Some(headers)
}