///|
pub(all) enum FreshnessSource {
SharedMaxAge
MaxAge
Expires
HeuristicLastModified
NoFreshness
} derive(Eq, Compare, Debug)
///|
pub fn FreshnessSource::label(self : FreshnessSource) -> String {
match self {
SharedMaxAge => "s-maxage"
MaxAge => "max-age"
Expires => "expires"
HeuristicLastModified => "heuristic-last-modified"
NoFreshness => "none"
}
}
///|
pub fn FreshnessSource::reason(self : FreshnessSource) -> CacheReasonCode {
match self {
SharedMaxAge => FreshSharedMaxAge
MaxAge => FreshMaxAge
Expires => FreshExpires
HeuristicLastModified => FreshHeuristic
NoFreshness => FreshNone
}
}
///|
pub(all) struct FreshnessCalculation {
lifetime : DeltaSeconds
source : FreshnessSource
date_value : Timestamp
expires_value : Timestamp?
last_modified_value : Timestamp?
} derive(Eq, Debug)
///|
fn response_date_or_time(response : ResponseMeta) -> Timestamp {
match response.headers.get_first("date") {
Some(value) => parse_http_date(value).unwrap_or(response.response_time)
None => response.response_time
}
}
///|
fn parsed_header_date(headers : HeaderMap, name : String) -> Timestamp? {
match headers.get_first(name) {
Some(value) => parse_http_date(value)
None => None
}
}
///|
/// Calculate freshness lifetime using RFC directive precedence. Shared caches
/// prefer `s-maxage`; explicit expiration precedes the conservative
/// Last-Modified heuristic.
pub fn calculate_freshness_lifetime(
response : ResponseMeta,
options : CacheOptions,
) -> FreshnessCalculation {
let directives = cache_control_from_headers(response.headers)
let date_value = response_date_or_time(response)
let expires_value = parsed_header_date(response.headers, "expires")
let last_modified_value = parsed_header_date(
response.headers,
"last-modified",
)
if options.mode is Shared && directives.shared_max_age is Some(delta) {
return FreshnessCalculation::{
lifetime: delta,
source: SharedMaxAge,
date_value,
expires_value,
last_modified_value,
}
}
if directives.max_age is Some(delta) {
return FreshnessCalculation::{
lifetime: delta,
source: MaxAge,
date_value,
expires_value,
last_modified_value,
}
}
if response.headers.contains("expires") {
let lifetime = match expires_value {
Some(expires) => expires.elapsed_since(date_value)
None => DeltaSeconds::zero()
}
return FreshnessCalculation::{
lifetime,
source: Expires,
date_value,
expires_value,
last_modified_value,
}
}
if options.allow_heuristic_freshness &&
last_modified_value is Some(last_modified) {
let resource_age = date_value.elapsed_since(last_modified)
let fraction = options.heuristic_fraction_per_mille
let numerator : Int64 = if fraction <= 0 { 0 } else { fraction.to_int64() }
let lifetime = resource_age
.saturating_scale(numerator, 1_000)
.min(options.max_heuristic_lifetime)
return FreshnessCalculation::{
lifetime,
source: HeuristicLastModified,
date_value,
expires_value,
last_modified_value,
}
}
FreshnessCalculation::{
lifetime: DeltaSeconds::zero(),
source: NoFreshness,
date_value,
expires_value,
last_modified_value,
}
}