net: Stop using legacy time in the HTTP and CORS caches (#33259)

This is part of switching away from using a very old version of `time`.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2024-08-30 14:54:02 +02:00 committed by GitHub
parent 817a91f2ac
commit a58d816319
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 65 deletions

View file

@ -9,11 +9,12 @@
//! This library will eventually become the core of the Fetch crate //! This library will eventually become the core of the Fetch crate
//! with CORSRequest being expanded into FetchRequest (etc) //! with CORSRequest being expanded into FetchRequest (etc)
use std::time::{Duration, Instant};
use http::header::HeaderName; use http::header::HeaderName;
use http::Method; use http::Method;
use net_traits::request::{CredentialsMode, Origin, Request}; use net_traits::request::{CredentialsMode, Origin, Request};
use servo_url::ServoUrl; use servo_url::ServoUrl;
use time::{self, Timespec};
/// Union type for CORS cache entries /// Union type for CORS cache entries
/// ///
@ -45,17 +46,17 @@ impl HeaderOrMethod {
pub struct CorsCacheEntry { pub struct CorsCacheEntry {
pub origin: Origin, pub origin: Origin,
pub url: ServoUrl, pub url: ServoUrl,
pub max_age: u32, pub max_age: Duration,
pub credentials: bool, pub credentials: bool,
pub header_or_method: HeaderOrMethod, pub header_or_method: HeaderOrMethod,
created: Timespec, created: Instant,
} }
impl CorsCacheEntry { impl CorsCacheEntry {
fn new( fn new(
origin: Origin, origin: Origin,
url: ServoUrl, url: ServoUrl,
max_age: u32, max_age: Duration,
credentials: bool, credentials: bool,
header_or_method: HeaderOrMethod, header_or_method: HeaderOrMethod,
) -> CorsCacheEntry { ) -> CorsCacheEntry {
@ -65,7 +66,7 @@ impl CorsCacheEntry {
max_age, max_age,
credentials, credentials,
header_or_method, header_or_method,
created: time::now().to_timespec(), created: Instant::now(),
} }
} }
} }
@ -107,10 +108,10 @@ impl CorsCache {
/// Remove old entries /// Remove old entries
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
let CorsCache(buf) = self.clone(); let CorsCache(buf) = self.clone();
let now = time::now().to_timespec(); let now = Instant::now();
let new_buf: Vec<CorsCacheEntry> = buf let new_buf: Vec<CorsCacheEntry> = buf
.into_iter() .into_iter()
.filter(|e| now.sec < e.created.sec + e.max_age as i64) .filter(|e| now < e.created + e.max_age)
.collect(); .collect();
*self = CorsCache(new_buf); *self = CorsCache(new_buf);
} }
@ -129,7 +130,7 @@ impl CorsCache {
&mut self, &mut self,
request: &Request, request: &Request,
header_name: &HeaderName, header_name: &HeaderName,
new_max_age: u32, new_max_age: Duration,
) -> bool { ) -> bool {
match self match self
.find_entry_by_header(request, header_name) .find_entry_by_header(request, header_name)
@ -163,7 +164,7 @@ impl CorsCache {
&mut self, &mut self,
request: &Request, request: &Request,
method: Method, method: Method,
new_max_age: u32, new_max_age: Duration,
) -> bool { ) -> bool {
match self match self
.find_entry_by_method(request, method.clone()) .find_entry_by_method(request, method.clone())

View file

@ -11,7 +11,7 @@ use std::collections::HashMap;
use std::ops::Bound; use std::ops::Bound;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex; use std::sync::Mutex;
use std::time::SystemTime; use std::time::{Duration, Instant, SystemTime};
use headers::{ use headers::{
CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary, CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
@ -30,7 +30,6 @@ use net_traits::{FetchMetadata, Metadata, ResourceFetchTiming};
use servo_arc::Arc; use servo_arc::Arc;
use servo_config::pref; use servo_config::pref;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use time::{Duration, Timespec, Tm};
use tokio::sync::mpsc::{unbounded_channel as unbounded, UnboundedSender as TokioSender}; use tokio::sync::mpsc::{unbounded_channel as unbounded, UnboundedSender as TokioSender};
use crate::fetch::methods::{Data, DoneChannel}; use crate::fetch::methods::{Data, DoneChannel};
@ -75,7 +74,7 @@ struct MeasurableCachedResource {
raw_status: Option<(u16, Vec<u8>)>, raw_status: Option<(u16, Vec<u8>)>,
url_list: Vec<ServoUrl>, url_list: Vec<ServoUrl>,
expires: Duration, expires: Duration,
last_validated: Tm, last_validated: Instant,
} }
impl MallocSizeOf for CachedResource { impl MallocSizeOf for CachedResource {
@ -178,14 +177,13 @@ fn response_is_cacheable(metadata: &Metadata) -> bool {
/// <https://tools.ietf.org/html/rfc7234#section-4.2.3> /// <https://tools.ietf.org/html/rfc7234#section-4.2.3>
fn calculate_response_age(response: &Response) -> Duration { fn calculate_response_age(response: &Response) -> Duration {
// TODO: follow the spec more closely (Date headers, request/response lag, ...) // TODO: follow the spec more closely (Date headers, request/response lag, ...)
if let Some(secs) = response.headers.get(header::AGE) { response
if let Ok(seconds_string) = secs.to_str() { .headers
if let Ok(secs) = seconds_string.parse::<i64>() { .get(header::AGE)
return Duration::seconds(secs); .and_then(|age_header| age_header.to_str().ok())
} .and_then(|age_string| age_string.parse::<u64>().ok())
} .map(|seconds| Duration::from_secs(seconds))
} .unwrap_or_default()
Duration::seconds(0i64)
} }
/// Determine the expiry date from relevant headers, /// Determine the expiry date from relevant headers,
@ -193,34 +191,28 @@ fn calculate_response_age(response: &Response) -> Duration {
fn get_response_expiry(response: &Response) -> Duration { fn get_response_expiry(response: &Response) -> Duration {
// Calculating Freshness Lifetime <https://tools.ietf.org/html/rfc7234#section-4.2.1> // Calculating Freshness Lifetime <https://tools.ietf.org/html/rfc7234#section-4.2.1>
let age = calculate_response_age(response); let age = calculate_response_age(response);
let now = SystemTime::now();
if let Some(directives) = response.headers.typed_get::<CacheControl>() { if let Some(directives) = response.headers.typed_get::<CacheControl>() {
if directives.no_cache() { if directives.no_cache() {
// Requires validation on first use. // Requires validation on first use.
return Duration::seconds(0i64); return Duration::ZERO;
} }
if let Some(secs) = directives.max_age().or(directives.s_max_age()) { if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
let max_age = Duration::from_std(secs).unwrap();
if max_age < age { if max_age < age {
return Duration::seconds(0i64); return Duration::ZERO;
} }
return max_age - age; return max_age - age;
} }
} }
match response.headers.typed_get::<Expires>() { match response.headers.typed_get::<Expires>() {
Some(t) => { Some(expiry) => {
// store the period of time from now until expiry // `duration_since` fails if `now` is later than `expiry_time` in which case,
let t: SystemTime = t.into(); // this whole thing return `Duration::ZERO`.
let t = t.duration_since(SystemTime::UNIX_EPOCH).unwrap(); let expiry_time: SystemTime = expiry.into();
let desired = Timespec::new(t.as_secs() as i64, 0); return expiry_time.duration_since(now).unwrap_or(Duration::ZERO);
let current = time::now().to_timespec();
if desired > current {
return desired - current;
}
return Duration::seconds(0i64);
}, },
// Malformed Expires header, shouldn't be used to construct a valid response. // Malformed Expires header, shouldn't be used to construct a valid response.
None if response.headers.contains_key(header::EXPIRES) => return Duration::seconds(0i64), None if response.headers.contains_key(header::EXPIRES) => return Duration::ZERO,
_ => {}, _ => {},
} }
// Calculating Heuristic Freshness // Calculating Heuristic Freshness
@ -229,22 +221,20 @@ fn get_response_expiry(response: &Response) -> Duration {
// <https://tools.ietf.org/html/rfc7234#section-5.5.4> // <https://tools.ietf.org/html/rfc7234#section-5.5.4>
// Since presently we do not generate a Warning header field with a 113 warn-code, // Since presently we do not generate a Warning header field with a 113 warn-code,
// 24 hours minus response age is the max for heuristic calculation. // 24 hours minus response age is the max for heuristic calculation.
let max_heuristic = Duration::hours(24) - age; let max_heuristic = Duration::from_secs(24 * 60 * 60) - age;
let heuristic_freshness = if let Some(last_modified) = let heuristic_freshness = if let Some(last_modified) =
// If the response has a Last-Modified header field, // If the response has a Last-Modified header field,
// caches are encouraged to use a heuristic expiration value // caches are encouraged to use a heuristic expiration value
// that is no more than some fraction of the interval since that time. // that is no more than some fraction of the interval since that time.
response.headers.typed_get::<LastModified>() response.headers.typed_get::<LastModified>()
{ {
let current = time::now().to_timespec(); // `time_since_last_modified` will be `Duration::ZERO` if `last_modified` is
// after `now`.
let last_modified: SystemTime = last_modified.into(); let last_modified: SystemTime = last_modified.into();
let last_modified = last_modified let time_since_last_modified = now.duration_since(last_modified).unwrap_or_default();
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let last_modified = Timespec::new(last_modified.as_secs() as i64, 0);
// A typical setting of this fraction might be 10%.
let raw_heuristic_calc = (current - last_modified) / 10;
// A typical setting of this fraction might be 10%.
let raw_heuristic_calc = time_since_last_modified / 10;
if raw_heuristic_calc < max_heuristic { if raw_heuristic_calc < max_heuristic {
raw_heuristic_calc raw_heuristic_calc
} else { } else {
@ -265,36 +255,35 @@ fn get_response_expiry(response: &Response) -> Duration {
} }
} }
// Requires validation upon first use as default. // Requires validation upon first use as default.
Duration::seconds(0i64) Duration::ZERO
} }
/// Request Cache-Control Directives /// Request Cache-Control Directives
/// <https://tools.ietf.org/html/rfc7234#section-5.2.1> /// <https://tools.ietf.org/html/rfc7234#section-5.2.1>
fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Duration) -> Duration { fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Duration) -> Duration {
let directive = match request.headers.typed_get::<CacheControl>() { let Some(directive) = request.headers.typed_get::<CacheControl>() else {
Some(data) => data, return expires;
None => return expires,
}; };
if let Some(max_age) = directive.max_stale() { if let Some(max_age) = directive.max_stale() {
return expires + Duration::from_std(max_age).unwrap(); return expires + max_age;
}
if let Some(max_age) = directive.max_age() {
let max_age = Duration::from_std(max_age).unwrap();
if expires > max_age {
return Duration::min_value();
}
return expires - max_age;
} }
match directive.max_age() {
Some(max_age) if expires > max_age => return Duration::ZERO,
Some(max_age) => return expires - max_age,
None => {},
};
if let Some(min_fresh) = directive.min_fresh() { if let Some(min_fresh) = directive.min_fresh() {
let min_fresh = Duration::from_std(min_fresh).unwrap();
if expires < min_fresh { if expires < min_fresh {
return Duration::min_value(); return Duration::ZERO;
} }
return expires - min_fresh; return expires - min_fresh;
} }
if directive.no_cache() || directive.no_store() { if directive.no_cache() || directive.no_store() {
return Duration::min_value(); return Duration::ZERO;
} }
expires expires
@ -343,9 +332,8 @@ fn create_cached_response(
let expires = cached_resource.data.expires; let expires = cached_resource.data.expires;
let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires); let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
let now = Duration::seconds(time::now().to_timespec().sec); let time_since_validated = Instant::now() - cached_resource.data.last_validated;
let last_validated = Duration::seconds(cached_resource.data.last_validated.to_timespec().sec);
let time_since_validated = now - last_validated;
// TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1> // TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
// TODO: if this cache is to be considered shared, take proxy-revalidate into account // TODO: if this cache is to be considered shared, take proxy-revalidate into account
// <https://tools.ietf.org/html/rfc7234#section-5.2.2.7> // <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
@ -808,7 +796,7 @@ impl HttpCache {
let entry_key = CacheKey::from_servo_url(url); let entry_key = CacheKey::from_servo_url(url);
if let Some(cached_resources) = self.entries.get_mut(&entry_key) { if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
for cached_resource in cached_resources.iter_mut() { for cached_resource in cached_resources.iter_mut() {
cached_resource.data.expires = Duration::seconds(0i64); cached_resource.data.expires = Duration::ZERO;
} }
} }
} }
@ -892,7 +880,7 @@ impl HttpCache {
raw_status: response.raw_status.clone(), raw_status: response.raw_status.clone(),
url_list: response.url_list.clone(), url_list: response.url_list.clone(),
expires: expiry, expires: expiry,
last_validated: time::now(), last_validated: Instant::now(),
}), }),
}; };
let entry = self.entries.entry(entry_key).or_default(); let entry = self.entries.entry(entry_key).or_default();

View file

@ -2097,7 +2097,6 @@ async fn cors_preflight_fetch(
.typed_get::<AccessControlMaxAge>() .typed_get::<AccessControlMaxAge>()
.map(|acma| acma.into()) .map(|acma| acma.into())
.unwrap_or(Duration::from_secs(5)); .unwrap_or(Duration::from_secs(5));
let max_age = max_age.as_secs() as u32;
// Substep 10 // Substep 10
// TODO: Need to define what an imposed limit on max-age is // TODO: Need to define what an imposed limit on max-age is