mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2024-12-27 23:55:58 -05:00
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.
This commit is contained in:
parent
cc646b1519
commit
4584cfe3c1
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3295,6 +3295,7 @@ dependencies = [
|
|||||||
"governor",
|
"governor",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
|
"httpdate",
|
||||||
"idna 0.2.3",
|
"idna 0.2.3",
|
||||||
"job_scheduler",
|
"job_scheduler",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
|
@ -80,6 +80,7 @@ uuid = { version = "0.8.2", features = ["v4"] }
|
|||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
chrono-tz = "0.6.1"
|
chrono-tz = "0.6.1"
|
||||||
time = "0.2.27"
|
time = "0.2.27"
|
||||||
|
httpdate = "1.0"
|
||||||
|
|
||||||
# Job scheduler
|
# Job scheduler
|
||||||
job_scheduler = "1.2.1"
|
job_scheduler = "1.2.1"
|
||||||
|
@ -103,14 +103,19 @@ fn icon_internal(domain: String) -> Cached<Content<Vec<u8>>> {
|
|||||||
return Cached::ttl(
|
return Cached::ttl(
|
||||||
Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
|
Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
|
||||||
CONFIG.icon_cache_negttl(),
|
CONFIG.icon_cache_negttl(),
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
match get_icon(&domain) {
|
match get_icon(&domain) {
|
||||||
Some((icon, icon_type)) => {
|
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,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,41 +22,44 @@ pub fn routes() -> Vec<Route> {
|
|||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn web_index() -> Cached<Option<NamedFile>> {
|
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")]
|
#[get("/app-id.json")]
|
||||||
fn app_id() -> Cached<Content<Json<Value>>> {
|
fn app_id() -> Cached<Content<Json<Value>>> {
|
||||||
let content_type = ContentType::new("application", "fido.trusted-apps+json");
|
let content_type = ContentType::new("application", "fido.trusted-apps+json");
|
||||||
|
|
||||||
Cached::long(Content(
|
Cached::long(
|
||||||
content_type,
|
Content(
|
||||||
Json(json!({
|
content_type,
|
||||||
"trustedFacets": [
|
Json(json!({
|
||||||
{
|
"trustedFacets": [
|
||||||
"version": { "major": 1, "minor": 0 },
|
{
|
||||||
"ids": [
|
"version": { "major": 1, "minor": 0 },
|
||||||
// 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>:
|
"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
|
// "In the Web case, the FacetID MUST be the Web Origin [RFC6454]
|
||||||
// a URI with an empty path. Default ports are omitted and any
|
// of the web page triggering the FIDO operation, written as
|
||||||
// path component is ignored."
|
// 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
|
// This leaves it unclear as to whether the path must be empty,
|
||||||
// the safe side, use a proper web origin (with empty path).
|
// or whether it can be non-empty and will be ignored. To be on
|
||||||
&CONFIG.domain_origin(),
|
// the safe side, use a proper web origin (with empty path).
|
||||||
"ios:bundle-id:com.8bit.bitwarden",
|
&CONFIG.domain_origin(),
|
||||||
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ]
|
"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
|
#[get("/<p..>", rank = 10)] // Only match this if the other routes don't match
|
||||||
fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
|
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>")]
|
#[get("/attachments/<uuid>/<file_id>")]
|
||||||
|
51
src/util.rs
51
src/util.rs
@ -11,6 +11,10 @@ use rocket::{
|
|||||||
Data, Request, Response, Rocket,
|
Data, Request, Response, Rocket,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use httpdate::HttpDate;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
pub struct AppHeaders();
|
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> {
|
impl<R> Cached<R> {
|
||||||
pub fn long(r: R) -> Cached<R> {
|
pub fn long(response: R, is_immutable: bool) -> Cached<R> {
|
||||||
// 7 days
|
Self {
|
||||||
Self::ttl(r, 604800)
|
response,
|
||||||
|
is_immutable,
|
||||||
|
ttl: 604800, // 7 days
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn short(r: R) -> Cached<R> {
|
pub fn short(response: R, is_immutable: bool) -> Cached<R> {
|
||||||
// 10 minutes
|
Self {
|
||||||
Self(r, String::from("public, max-age=600"))
|
response,
|
||||||
|
is_immutable,
|
||||||
|
ttl: 600, // 10 minutes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ttl(r: R, ttl: u64) -> Cached<R> {
|
pub fn ttl(response: R, ttl: u64, is_immutable: bool) -> Cached<R> {
|
||||||
Self(r, format!("public, immutable, max-age={}", ttl))
|
Self {
|
||||||
|
response,
|
||||||
|
is_immutable,
|
||||||
|
ttl: ttl,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
|
impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
|
||||||
fn respond_to(self, req: &Request) -> response::Result<'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) => {
|
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)
|
Ok(res)
|
||||||
}
|
}
|
||||||
e @ Err(_) => e,
|
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>
|
pub fn retry_db<F, T, E>(func: F, max_tries: u32) -> Result<T, E>
|
||||||
where
|
where
|
||||||
F: Fn() -> Result<T, E>,
|
F: Fn() -> Result<T, E>,
|
||||||
|
Loading…
Reference in New Issue
Block a user