mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
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:
parent
817a91f2ac
commit
a58d816319
3 changed files with 53 additions and 65 deletions
|
@ -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())
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue