commit 4584cfe3c18820d8235628cdf629655abd601bc7
parent cc646b15193cbb2bcc10dcde93416221e518cefb
Author: Jake Howard <git@theorangeone.net>
Date: Tue, 28 Dec 2021 16:24:42 +0000
Additionally set expires header when caching responses
Browsers are rather smart, but also dumb. This uses the `Expires` header
alongside `cache-control` to better prompt the browser to actually
cache.
Unfortunately, firefox still tries to "race" its own cache, in an
attempt to respond to requests faster, so still ends up making a bunch
of requests which could have been cached. Doesn't appear there's any way
around this.
Diffstat:
5 files changed, 75 insertions(+), 40 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3295,6 +3295,7 @@ dependencies = [
"governor",
"handlebars",
"html5ever",
+ "httpdate",
"idna 0.2.3",
"job_scheduler",
"jsonwebtoken",
diff --git a/Cargo.toml b/Cargo.toml
@@ -80,6 +80,7 @@ uuid = { version = "0.8.2", features = ["v4"] }
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = "0.6.1"
time = "0.2.27"
+httpdate = "1.0"
# Job scheduler
job_scheduler = "1.2.1"
diff --git a/src/api/icons.rs b/src/api/icons.rs
@@ -103,14 +103,19 @@ fn icon_internal(domain: String) -> Cached<Content<Vec<u8>>> {
return Cached::ttl(
Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
CONFIG.icon_cache_negttl(),
+ true,
);
}
match get_icon(&domain) {
Some((icon, icon_type)) => {
- Cached::ttl(Content(ContentType::new("image", icon_type), icon), CONFIG.icon_cache_ttl())
+ Cached::ttl(Content(ContentType::new("image", icon_type), icon), CONFIG.icon_cache_ttl(), true)
}
- _ => Cached::ttl(Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()), CONFIG.icon_cache_negttl()),
+ _ => Cached::ttl(
+ Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
+ CONFIG.icon_cache_negttl(),
+ true,
+ ),
}
}
diff --git a/src/api/web.rs b/src/api/web.rs
@@ -22,41 +22,44 @@ pub fn routes() -> Vec<Route> {
#[get("/")]
fn web_index() -> Cached<Option<NamedFile>> {
- Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("index.html")).ok())
+ Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("index.html")).ok(), false)
}
#[get("/app-id.json")]
fn app_id() -> Cached<Content<Json<Value>>> {
let content_type = ContentType::new("application", "fido.trusted-apps+json");
- Cached::long(Content(
- content_type,
- Json(json!({
- "trustedFacets": [
- {
- "version": { "major": 1, "minor": 0 },
- "ids": [
- // Per <https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application>:
- //
- // "In the Web case, the FacetID MUST be the Web Origin [RFC6454]
- // of the web page triggering the FIDO operation, written as
- // a URI with an empty path. Default ports are omitted and any
- // path component is ignored."
- //
- // This leaves it unclear as to whether the path must be empty,
- // or whether it can be non-empty and will be ignored. To be on
- // the safe side, use a proper web origin (with empty path).
- &CONFIG.domain_origin(),
- "ios:bundle-id:com.8bit.bitwarden",
- "android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ]
- }]
- })),
- ))
+ Cached::long(
+ Content(
+ content_type,
+ Json(json!({
+ "trustedFacets": [
+ {
+ "version": { "major": 1, "minor": 0 },
+ "ids": [
+ // Per <https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application>:
+ //
+ // "In the Web case, the FacetID MUST be the Web Origin [RFC6454]
+ // of the web page triggering the FIDO operation, written as
+ // a URI with an empty path. Default ports are omitted and any
+ // path component is ignored."
+ //
+ // This leaves it unclear as to whether the path must be empty,
+ // or whether it can be non-empty and will be ignored. To be on
+ // the safe side, use a proper web origin (with empty path).
+ &CONFIG.domain_origin(),
+ "ios:bundle-id:com.8bit.bitwarden",
+ "android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ]
+ }]
+ })),
+ ),
+ true,
+ )
}
#[get("/<p..>", rank = 10)] // Only match this if the other routes don't match
fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
- Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).ok())
+ Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).ok(), true)
}
#[get("/attachments/<uuid>/<file_id>")]
diff --git a/src/util.rs b/src/util.rs
@@ -11,6 +11,10 @@ use rocket::{
Data, Request, Response, Rocket,
};
+use httpdate::HttpDate;
+use std::thread::sleep;
+use std::time::{Duration, SystemTime};
+
use crate::CONFIG;
pub struct AppHeaders();
@@ -99,29 +103,52 @@ impl Fairing for Cors {
}
}
-pub struct Cached<R>(R, String);
+pub struct Cached<R> {
+ response: R,
+ is_immutable: bool,
+ ttl: u64,
+}
impl<R> Cached<R> {
- pub fn long(r: R) -> Cached<R> {
- // 7 days
- Self::ttl(r, 604800)
+ pub fn long(response: R, is_immutable: bool) -> Cached<R> {
+ Self {
+ response,
+ is_immutable,
+ ttl: 604800, // 7 days
+ }
}
- pub fn short(r: R) -> Cached<R> {
- // 10 minutes
- Self(r, String::from("public, max-age=600"))
+ pub fn short(response: R, is_immutable: bool) -> Cached<R> {
+ Self {
+ response,
+ is_immutable,
+ ttl: 600, // 10 minutes
+ }
}
- pub fn ttl(r: R, ttl: u64) -> Cached<R> {
- Self(r, format!("public, immutable, max-age={}", ttl))
+ pub fn ttl(response: R, ttl: u64, is_immutable: bool) -> Cached<R> {
+ Self {
+ response,
+ is_immutable,
+ ttl: ttl,
+ }
}
}
impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
fn respond_to(self, req: &Request) -> response::Result<'r> {
- match self.0.respond_to(req) {
+ let cache_control_header = if self.is_immutable {
+ format!("public, immutable, max-age={}", self.ttl)
+ } else {
+ format!("public, max-age={}", self.ttl)
+ };
+
+ let time_now = SystemTime::now();
+
+ match self.response.respond_to(req) {
Ok(mut res) => {
- res.set_raw_header("Cache-Control", self.1);
+ res.set_raw_header("Cache-Control", cache_control_header);
+ res.set_raw_header("Expires", HttpDate::from(time_now + Duration::from_secs(self.ttl)).to_string());
Ok(res)
}
e @ Err(_) => e,
@@ -551,8 +578,6 @@ where
}
}
-use std::{thread::sleep, time::Duration};
-
pub fn retry_db<F, T, E>(func: F, max_tries: u32) -> Result<T, E>
where
F: Fn() -> Result<T, E>,