From 8cd215cdc2fa6b710c54926fa1f9ea4cb1909f2b Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 29 Jan 2017 19:03:35 +0100 Subject: [PATCH] Improve handling of static files. Use static files handling from updated ructe rather than local handling, and use rsass rather than sass-rs and sass-sys. Static files are now kept in the binary (no more need to access the build directory at runtime). Also, there is an admin command to save the static files to a directory, for the benefit of fronting web servers and backwards compatibility. --- Cargo.toml | 9 ++--- src/adm/mod.rs | 1 + src/adm/storestatics.rs | 26 +++++++++++++ src/build.rs | 75 ++++++------------------------------- src/main.rs | 21 ++++++++--- src/rphotosadm.rs | 12 +++++- templates/page_base.rs.html | 3 +- 7 files changed, 71 insertions(+), 76 deletions(-) create mode 100644 src/adm/storestatics.rs diff --git a/Cargo.toml b/Cargo.toml index c422873..f018cc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,9 @@ authors = ["Rasmus Kaj "] build = "src/build.rs" [build-dependencies] -sass-rs = "*" -sass-sys = "*" -md5 = "*" -rustc-serialize = "*" -flate2 = "*" -brotli2 = "*" diesel_codegen_syntex = { version = "*", features = ["postgres", "syntex"] } ructe = "*" +rsass = "*" [[bin]] name = "rphotoserver" @@ -46,3 +41,5 @@ r2d2-diesel = "*" djangohashers = "*" rand = "*" memcached-rs = "^0.1" +flate2 = "*" +brotli2 = "*" diff --git a/src/adm/mod.rs b/src/adm/mod.rs index b8345b0..ac75d88 100644 --- a/src/adm/mod.rs +++ b/src/adm/mod.rs @@ -4,3 +4,4 @@ pub mod readkpa; pub mod result; pub mod stats; pub mod users; +pub mod storestatics; diff --git a/src/adm/storestatics.rs b/src/adm/storestatics.rs new file mode 100644 index 0000000..e53dfdd --- /dev/null +++ b/src/adm/storestatics.rs @@ -0,0 +1,26 @@ +use adm::result::Error; +use brotli2::write::BrotliEncoder; +use flate2::{Compression, FlateWriteExt}; +use std::fs::{File, create_dir_all}; +use std::io::prelude::*; +use std::path::Path; + +pub fn to_dir(dir: &str) -> Result<(), Error> { + let dir: &Path = dir.as_ref(); + try!(create_dir_all(&dir)); + for s in STATICS { + try!(File::create(dir.join(s.name)) + .and_then(|mut f| f.write(s.content))); + + try!(File::create(dir.join(format!("{}.gz", s.name))) + .map(|f| f.gz_encode(Compression::Best)) + .and_then(|mut f| f.write(s.content))); + + try!(File::create(dir.join(format!("{}.br", s.name))) + .map(|f| BrotliEncoder::new(f, 11)) + .and_then(|mut f| f.write(s.content))); + } + Ok(()) +} + +include!(concat!(env!("OUT_DIR"), "/templates/statics.rs")); diff --git a/src/build.rs b/src/build.rs index 36ead1e..e02405b 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,31 +1,23 @@ -extern crate brotli2; extern crate diesel_codegen_syntex as diesel_codegen; -extern crate flate2; -extern crate md5; -extern crate rustc_serialize as serialize; -extern crate sass_rs; -extern crate sass_sys; +extern crate rsass; extern crate ructe; -use brotli2::write::BrotliEncoder; -use flate2::{Compression, FlateWriteExt}; -use ructe::compile_templates; -use sass_rs::dispatcher::Dispatcher; -use sass_rs::sass_context::SassFileContext; -use serialize::base64::{self, ToBase64}; +use rsass::{OutputStyle, compile_scss_file}; +use ructe::{compile_static_files, compile_templates}; use std::env; use std::fs::{File, create_dir_all}; use std::io::Write; use std::path::{Path, PathBuf}; -use std::thread; fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); prepare_diesel(&out_dir); - do_sassify(&out_dir); + let css_dir = out_dir.join("tmpcss"); + do_sassify(&css_dir); let template_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) .join("templates"); - compile_templates(&template_dir, &out_dir).unwrap(); + compile_static_files(&css_dir, &out_dir).expect("statics"); + compile_templates(&template_dir, &out_dir).expect("templates"); } pub fn prepare_diesel(out_dir: &Path) { @@ -38,59 +30,16 @@ pub fn prepare_diesel(out_dir: &Path) { println!("cargo:rerun-if-changed=src/lib.in.rs"); } -pub fn do_sassify(out_dir: &Path) { - let static_dir = out_dir.join("static").join("static"); +pub fn do_sassify(static_dir: &Path) { create_dir_all(&static_dir).unwrap(); - let css = compile("photos.scss").unwrap(); - let css = css.as_bytes(); - let filename = format!("style-{}.css", checksum_slug(&css)); + let css = compile_scss_file("photos.scss".as_ref(), + OutputStyle::Compressed).unwrap(); - File::create(static_dir.join(&filename)) - .and_then(|mut f| f.write(css)) + File::create(static_dir.join("style.css")) + .and_then(|mut f| f.write(&css)) .expect("Writing css"); - File::create(static_dir.join(format!("{}.gz", filename))) - .map(|f| f.gz_encode(Compression::Best)) - .and_then(|mut f| f.write(css)) - .expect("Writing gzipped css"); - File::create(static_dir.join(format!("{}.br", filename))) - .map(|f| BrotliEncoder::new(f, 11)) - .and_then(|mut f| f.write(css)) - .expect("Writing brotli compressed css"); - - File::create(&out_dir.join("stylelink")) - .and_then(|mut f| { - writeln!(f, - "\"\"", - filename) - }) - .expect("Writing stylelink"); // TODO Find any referenced files! println!("cargo:rerun-if-changed=photos.scss"); } - -/// A short and url-safe checksum string from string data. -fn checksum_slug(data: &[u8]) -> String { - md5::compute(data)[9..].to_base64(base64::URL_SAFE) -} - -/// Setup the sass environment and compile a file. -fn compile(filename: &str) -> Result { - let mut file_context = SassFileContext::new(filename); - // options.set_output_style(COMPRESSED) or similar, when supported. - if let Ok(mut opt) = file_context.sass_context.sass_options.write() { - unsafe { - sass_sys::sass_option_set_output_style( - opt.raw.get_mut(), - sass_sys::SASS_STYLE_COMPRESSED); - } - } - let options = file_context.sass_context.sass_options.clone(); - thread::spawn(move || { - let dispatcher = Dispatcher::build(vec![], options); - while dispatcher.dispatch().is_ok() {} - }); - file_context.compile() -} diff --git a/src/main.rs b/src/main.rs index de78ee8..9989961 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ use diesel::prelude::*; use dotenv::dotenv; use hyper::header::{Expires, HttpDate}; use nickel::{FormBody, Halt, HttpRouter, MediaType, MiddlewareResult, Nickel, - Request, Response, StaticFilesHandler}; + Request, Response}; use nickel::extensions::response::Redirect; use nickel::status::StatusCode; use nickel_diesel::{DieselMiddleware, DieselRequestExtensions}; @@ -100,13 +100,10 @@ fn main() { let mut server = Nickel::new(); server.utilize(RequestLoggerMiddleware); + server.get("/static/:file.:ext", wrap!(static_file: file, ext)); server.utilize(MemcacheMiddleware::new (vec![("tcp://127.0.0.1:11211".into(), 1)])); server.utilize(SessionMiddleware::new(&jwt_key())); - // TODO This is a "build" location, not an "install" location ... - let staticdir = concat!(env!("OUT_DIR"), "/static/"); - info!("Serving static files from {}", staticdir); - server.utilize(StaticFilesHandler::new(staticdir)); let dm: DieselMiddleware = DieselMiddleware::new(&dburl(), 5, Box::new(NopErrorHandler)).unwrap(); server.utilize(dm); @@ -308,6 +305,20 @@ fn place_all<'mw>(req: &mut Request, query.order(place_name).load(c).expect("List places"))) } +fn static_file<'mw>(_req: &mut Request, + mut res: Response<'mw>, + name: String, + ext: String) + -> MiddlewareResult<'mw> { + use templates::statics::StaticFile; + if let Some(s) = StaticFile::get(&format!("{}.{}", name, ext)) { + res.set(ext.parse().unwrap_or(MediaType::Bin)); + res.set(Expires(HttpDate(time::now() + Duration::days(300)))); + return res.send(s.content); + } + res.error(StatusCode::NotFound, "No such file") +} + fn place_one<'mw>(req: &mut Request, res: Response<'mw>, tslug: String) diff --git a/src/rphotosadm.rs b/src/rphotosadm.rs index d738a56..080ee27 100644 --- a/src/rphotosadm.rs +++ b/src/rphotosadm.rs @@ -1,5 +1,6 @@ //! Just fooling around with different ways to count images per year. extern crate rphotos; +extern crate brotli2; extern crate clap; extern crate chrono; #[macro_use] @@ -7,6 +8,7 @@ extern crate diesel; extern crate djangohashers; extern crate dotenv; extern crate env_logger; +extern crate flate2; extern crate rand; extern crate rexif; #[macro_use] @@ -18,7 +20,7 @@ mod adm; mod env; mod photosdir; -use adm::{findphotos, makepublic, readkpa, users}; +use adm::{findphotos, makepublic, readkpa, users, storestatics}; use adm::result::Error; use adm::stats::show_stats; use clap::{App, Arg, ArgMatches, SubCommand}; @@ -66,6 +68,11 @@ fn main() { .help("Image path to make public")) .after_help("The image path(s) are relative to the \ image root.")) + .subcommand(SubCommand::with_name("storestatics") + .about("Store statics as files for a web server") + .arg(Arg::with_name("DIR") + .required(true) + .help("Directory to store the files in"))) .get_matches(); match run(args) { @@ -122,6 +129,9 @@ fn run(args: ArgMatches) -> Result<(), Error> { ("userpass", Some(args)) => { users::passwd(&try!(get_db()), args.value_of("USER").unwrap()) } + ("storestatics", Some(args)) => { + storestatics::to_dir(args.value_of("DIR").unwrap()) + } _ => Ok(println!("No subcommand given.\n\n{}", args.usage())), } } diff --git a/templates/page_base.rs.html b/templates/page_base.rs.html index 03defd9..730aba7 100644 --- a/templates/page_base.rs.html +++ b/templates/page_base.rs.html @@ -1,5 +1,6 @@ @use ::Link; @use templates::head; +@use templates::statics::style_css; @(title: &str, lpath: Vec, user: Option, content: Content) @@ -8,7 +9,7 @@ @title - @Html(include!(concat!(env!("OUT_DIR"), "/stylelink"))) + @:head(lpath, user)