mirror of
https://github.com/denoland/deno.git
synced 2025-08-03 18:38:33 +00:00
feat(lsp): registry suggestion cache respects cache headers (#13010)
Fixes #9931
This commit is contained in:
parent
69ad5f0e78
commit
2347e60934
7 changed files with 286 additions and 22 deletions
152
cli/http_util.rs
152
cli/http_util.rs
|
@ -1,6 +1,9 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::auth_tokens::AuthToken;
|
||||
|
||||
use cache_control::Cachability;
|
||||
use cache_control::CacheControl;
|
||||
use chrono::DateTime;
|
||||
use deno_core::error::custom_error;
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
|
@ -13,6 +16,8 @@ use deno_runtime::deno_fetch::reqwest::Client;
|
|||
use deno_runtime::deno_fetch::reqwest::StatusCode;
|
||||
use log::debug;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// Construct the next uri based on base uri and location header fragment
|
||||
/// See <https://tools.ietf.org/html/rfc3986#section-4.2>
|
||||
|
@ -46,6 +51,153 @@ fn resolve_url_from_location(base_url: &Url, location: &str) -> Url {
|
|||
// Vec<(String, String)>
|
||||
pub type HeadersMap = HashMap<String, String>;
|
||||
|
||||
/// A structure used to determine if a entity in the http cache can be used.
|
||||
///
|
||||
/// This is heavily influenced by
|
||||
/// https://github.com/kornelski/rusty-http-cache-semantics which is BSD
|
||||
/// 2-Clause Licensed and copyright Kornel Lesiński
|
||||
pub(crate) struct CacheSemantics {
|
||||
cache_control: CacheControl,
|
||||
cached: SystemTime,
|
||||
headers: HashMap<String, String>,
|
||||
now: SystemTime,
|
||||
}
|
||||
|
||||
impl CacheSemantics {
|
||||
pub fn new(
|
||||
headers: HashMap<String, String>,
|
||||
cached: SystemTime,
|
||||
now: SystemTime,
|
||||
) -> Self {
|
||||
let cache_control = headers
|
||||
.get("cache-control")
|
||||
.map(|v| CacheControl::from_value(v).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
cache_control,
|
||||
cached,
|
||||
headers,
|
||||
now,
|
||||
}
|
||||
}
|
||||
|
||||
fn age(&self) -> Duration {
|
||||
let mut age = self.age_header_value();
|
||||
|
||||
if let Ok(resident_time) = self.now.duration_since(self.cached) {
|
||||
age += resident_time;
|
||||
}
|
||||
|
||||
age
|
||||
}
|
||||
|
||||
fn age_header_value(&self) -> Duration {
|
||||
Duration::from_secs(
|
||||
self
|
||||
.headers
|
||||
.get("age")
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_stale(&self) -> bool {
|
||||
self.max_age() <= self.age()
|
||||
}
|
||||
|
||||
fn max_age(&self) -> Duration {
|
||||
if self.cache_control.cachability == Some(Cachability::NoCache) {
|
||||
return Duration::from_secs(0);
|
||||
}
|
||||
|
||||
if self.headers.get("vary").map(|s| s.trim()) == Some("*") {
|
||||
return Duration::from_secs(0);
|
||||
}
|
||||
|
||||
if let Some(max_age) = self.cache_control.max_age {
|
||||
return max_age;
|
||||
}
|
||||
|
||||
let default_min_ttl = Duration::from_secs(0);
|
||||
|
||||
let server_date = self.raw_server_date();
|
||||
if let Some(expires) = self.headers.get("expires") {
|
||||
return match DateTime::parse_from_rfc2822(expires) {
|
||||
Err(_) => Duration::from_secs(0),
|
||||
Ok(expires) => {
|
||||
let expires = SystemTime::UNIX_EPOCH
|
||||
+ Duration::from_secs(expires.timestamp().max(0) as _);
|
||||
return default_min_ttl
|
||||
.max(expires.duration_since(server_date).unwrap_or_default());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(last_modified) = self.headers.get("last-modified") {
|
||||
if let Ok(last_modified) = DateTime::parse_from_rfc2822(last_modified) {
|
||||
let last_modified = SystemTime::UNIX_EPOCH
|
||||
+ Duration::from_secs(last_modified.timestamp().max(0) as _);
|
||||
if let Ok(diff) = server_date.duration_since(last_modified) {
|
||||
let secs_left = diff.as_secs() as f64 * 0.1;
|
||||
return default_min_ttl.max(Duration::from_secs(secs_left as _));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default_min_ttl
|
||||
}
|
||||
|
||||
fn raw_server_date(&self) -> SystemTime {
|
||||
self
|
||||
.headers
|
||||
.get("date")
|
||||
.and_then(|d| DateTime::parse_from_rfc2822(d).ok())
|
||||
.and_then(|d| {
|
||||
SystemTime::UNIX_EPOCH
|
||||
.checked_add(Duration::from_secs(d.timestamp() as _))
|
||||
})
|
||||
.unwrap_or(self.cached)
|
||||
}
|
||||
|
||||
/// Returns true if the cached value is "fresh" respecting cached headers,
|
||||
/// otherwise returns false.
|
||||
pub fn should_use(&self) -> bool {
|
||||
if self.cache_control.cachability == Some(Cachability::NoCache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(max_age) = self.cache_control.max_age {
|
||||
if self.age() > max_age {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(min_fresh) = self.cache_control.min_fresh {
|
||||
if self.time_to_live() < min_fresh {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_stale() {
|
||||
let has_max_stale = self.cache_control.max_stale.is_some();
|
||||
let allows_stale = has_max_stale
|
||||
&& self
|
||||
.cache_control
|
||||
.max_stale
|
||||
.map_or(true, |val| val > self.age() - self.max_age());
|
||||
if !allows_stale {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn time_to_live(&self) -> Duration {
|
||||
self.max_age().checked_sub(self.age()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FetchOnceResult {
|
||||
Code(Vec<u8>, HeadersMap),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue