From a2b2e37aa0099aac451067ebf4526aa82f5eca51 Mon Sep 17 00:00:00 2001 From: fdb-hiroshima <35889323+fdb-hiroshima@users.noreply.github.com> Date: Sat, 16 Mar 2019 15:33:28 +0100 Subject: [PATCH] Caching (#480) * add basic caching support * Use hash of static dir instead of rand * Add support for ETag --- build.rs | 37 ++++++++++++++++++++++++++++++++++++- src/main.rs | 1 + src/routes/mod.rs | 28 ++++++++++++++++++++++++---- src/template_utils.rs | 39 +++++++++++++++++++++++++++++++++++---- static/css/main.scss | 6 +++--- templates/base.rs.html | 10 +++++----- 6 files changed, 104 insertions(+), 17 deletions(-) diff --git a/build.rs b/build.rs index e9ff3876..a9d14f61 100644 --- a/build.rs +++ b/build.rs @@ -2,6 +2,38 @@ extern crate ructe; extern crate rsass; use ructe::*; use std::{env, fs::*, io::Write, path::PathBuf}; +use std::process::{Command, Stdio}; + +fn compute_static_hash() -> String { + //"find static/ -type f ! -path 'static/media/*' | sort | xargs stat --printf='%n %Y\n' | sha256sum" + + let find = Command::new("find") + .args(&["static/", "-type", "f", "!", "-path", "static/media/*"]) + .stdout(Stdio::piped()) + .spawn() + .expect("failed find command"); + + let sort = Command::new("sort") + .stdin(find.stdout.unwrap()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed sort command"); + + let xargs = Command::new("xargs") + .args(&["stat", "--printf='%n %Y\n'"]) + .stdin(sort.stdout.unwrap()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed xargs command"); + + let sha = Command::new("sha256sum") + .stdin(xargs.stdout.unwrap()) + .output() + .expect("failed sha256sum command"); + + String::from_utf8(sha.stdout).unwrap() +} + fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); @@ -16,8 +48,11 @@ fn main() { .expect("Error during SCSS compilation") ).expect("Couldn't write CSS output"); + let cache_id = &compute_static_hash()[..8]; println!("cargo:rerun-if-changed=target/deploy/plume-front.wasm"); copy("target/deploy/plume-front.wasm", "static/plume-front.wasm") .and_then(|_| read_to_string("target/deploy/plume-front.js")) - .and_then(|js| write("static/plume-front.js", js.replace("\"plume-front.wasm\"", "\"/static/plume-front.wasm\""))).ok(); + .and_then(|js| write("static/plume-front.js", js.replace("\"plume-front.wasm\"", &format!("\"/static/cached/{}/plume-front.wasm\"", cache_id)))).ok(); + + println!("cargo:rustc-env=CACHE_ID={}", cache_id) } diff --git a/src/main.rs b/src/main.rs index 4949d6ec..195eaad9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -189,6 +189,7 @@ Then try to restart Plume. routes::session::password_reset_form, routes::session::password_reset, + routes::plume_static_files, routes::static_files, routes::tags::tag, diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 0fd3e4bd..0fb2b190 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,6 +1,9 @@ use atom_syndication::{ContentBuilder, Entry, EntryBuilder, LinkBuilder, Person, PersonBuilder}; use rocket::{ - http::{RawStr, Status, uri::{FromUriParam, Query}}, + http::{ + RawStr, Status, uri::{FromUriParam, Query}, + hyper::header::{CacheControl, CacheDirective} + }, Outcome, request::{self, FromFormValue, FromRequest, Request}, response::NamedFile, @@ -101,7 +104,24 @@ pub mod user; pub mod search; pub mod well_known; -#[get("/static/", rank = 2)] -pub fn static_files(file: PathBuf) -> Option { - NamedFile::open(Path::new("static/").join(file)).ok() +#[derive(Responder)] +#[response()] +pub struct CachedFile { + inner: NamedFile, + cache_control: CacheControl +} + +#[get("/static/cached/<_build_id>/", rank = 2)] +pub fn plume_static_files(file: PathBuf, _build_id: &RawStr) -> Option { + static_files(file) +} + +#[get("/static/", rank = 3)] +pub fn static_files(file: PathBuf) -> Option { + NamedFile::open(Path::new("static/").join(file)).ok() + .map(|f| + CachedFile { + inner: f, + cache_control: CacheControl(vec![CacheDirective::MaxAge(60*60*24*30)]) + }) } diff --git a/src/template_utils.rs b/src/template_utils.rs index 39c31b52..854d2dac 100644 --- a/src/template_utils.rs +++ b/src/template_utils.rs @@ -1,19 +1,50 @@ use plume_models::{Connection, notifications::*, users::User}; -use rocket::response::Content; + +use rocket::http::{Method, Status}; +use rocket::http::hyper::header::{ETag, EntityTag}; +use rocket::request::Request; +use rocket::response::{self, Response, Responder, content::Html as HtmlCt}; use rocket_i18n::Catalog; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; use templates::Html; pub use askama_escape::escape; +pub static CACHE_NAME: &str = env!("CACHE_ID"); + pub type BaseContext<'a> = &'a(&'a Connection, &'a Catalog, Option); -pub type Ructe = Content>; +#[derive(Debug)] +pub struct Ructe(pub Vec); + +impl<'r> Responder<'r> for Ructe { + fn respond_to(self, r: &Request) -> response::Result<'r> { + //if method is not Get or page contain a form, no caching + if r.method() != Method::Get || self.0.windows(6).any(|w| w == b"
{ { - use rocket::{http::ContentType, response::Content}; use templates; let mut res = vec![]; @@ -23,7 +54,7 @@ macro_rules! render { $param ),* ).unwrap(); - Content(ContentType::HTML, res) + Ructe(res) } } } diff --git a/static/css/main.scss b/static/css/main.scss index c0c6f73b..d2b0de4f 100644 --- a/static/css/main.scss +++ b/static/css/main.scss @@ -1,8 +1,8 @@ /* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */ -@import url('/static/fonts/Route159/Route159.css'); -@import url('/static/fonts/Lora/Lora.css'); -@import url('/static/fonts/Playfair_Display/PlayfairDisplay.css'); +@import url('../fonts/Route159/Route159.css'); +@import url('../fonts/Lora/Lora.css'); +@import url('../fonts/Playfair_Display/PlayfairDisplay.css'); html { box-sizing: border-box; diff --git a/templates/base.rs.html b/templates/base.rs.html index 572261e3..4eeeced5 100644 --- a/templates/base.rs.html +++ b/templates/base.rs.html @@ -8,10 +8,10 @@ @title ⋅ @i18n!(ctx.1, "Plume") - - + + - + @:head() @@ -22,7 +22,7 @@