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.
This commit is contained in:
parent
698fb2c23b
commit
8cd215cdc2
@ -6,14 +6,9 @@ authors = ["Rasmus Kaj <kaj@kth.se>"]
|
|||||||
build = "src/build.rs"
|
build = "src/build.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
sass-rs = "*"
|
|
||||||
sass-sys = "*"
|
|
||||||
md5 = "*"
|
|
||||||
rustc-serialize = "*"
|
|
||||||
flate2 = "*"
|
|
||||||
brotli2 = "*"
|
|
||||||
diesel_codegen_syntex = { version = "*", features = ["postgres", "syntex"] }
|
diesel_codegen_syntex = { version = "*", features = ["postgres", "syntex"] }
|
||||||
ructe = "*"
|
ructe = "*"
|
||||||
|
rsass = "*"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rphotoserver"
|
name = "rphotoserver"
|
||||||
@ -46,3 +41,5 @@ r2d2-diesel = "*"
|
|||||||
djangohashers = "*"
|
djangohashers = "*"
|
||||||
rand = "*"
|
rand = "*"
|
||||||
memcached-rs = "^0.1"
|
memcached-rs = "^0.1"
|
||||||
|
flate2 = "*"
|
||||||
|
brotli2 = "*"
|
||||||
|
@ -4,3 +4,4 @@ pub mod readkpa;
|
|||||||
pub mod result;
|
pub mod result;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
pub mod storestatics;
|
||||||
|
26
src/adm/storestatics.rs
Normal file
26
src/adm/storestatics.rs
Normal file
@ -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"));
|
75
src/build.rs
75
src/build.rs
@ -1,31 +1,23 @@
|
|||||||
extern crate brotli2;
|
|
||||||
extern crate diesel_codegen_syntex as diesel_codegen;
|
extern crate diesel_codegen_syntex as diesel_codegen;
|
||||||
extern crate flate2;
|
extern crate rsass;
|
||||||
extern crate md5;
|
|
||||||
extern crate rustc_serialize as serialize;
|
|
||||||
extern crate sass_rs;
|
|
||||||
extern crate sass_sys;
|
|
||||||
extern crate ructe;
|
extern crate ructe;
|
||||||
|
|
||||||
use brotli2::write::BrotliEncoder;
|
use rsass::{OutputStyle, compile_scss_file};
|
||||||
use flate2::{Compression, FlateWriteExt};
|
use ructe::{compile_static_files, compile_templates};
|
||||||
use ructe::compile_templates;
|
|
||||||
use sass_rs::dispatcher::Dispatcher;
|
|
||||||
use sass_rs::sass_context::SassFileContext;
|
|
||||||
use serialize::base64::{self, ToBase64};
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{File, create_dir_all};
|
use std::fs::{File, create_dir_all};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
prepare_diesel(&out_dir);
|
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())
|
let template_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||||
.join("templates");
|
.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) {
|
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");
|
println!("cargo:rerun-if-changed=src/lib.in.rs");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_sassify(out_dir: &Path) {
|
pub fn do_sassify(static_dir: &Path) {
|
||||||
let static_dir = out_dir.join("static").join("static");
|
|
||||||
create_dir_all(&static_dir).unwrap();
|
create_dir_all(&static_dir).unwrap();
|
||||||
|
|
||||||
let css = compile("photos.scss").unwrap();
|
let css = compile_scss_file("photos.scss".as_ref(),
|
||||||
let css = css.as_bytes();
|
OutputStyle::Compressed).unwrap();
|
||||||
let filename = format!("style-{}.css", checksum_slug(&css));
|
|
||||||
|
|
||||||
File::create(static_dir.join(&filename))
|
File::create(static_dir.join("style.css"))
|
||||||
.and_then(|mut f| f.write(css))
|
.and_then(|mut f| f.write(&css))
|
||||||
.expect("Writing 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,
|
|
||||||
"\"<link rel='stylesheet' href='/static/{}' \
|
|
||||||
type='text/css'/>\"",
|
|
||||||
filename)
|
|
||||||
})
|
|
||||||
.expect("Writing stylelink");
|
|
||||||
|
|
||||||
// TODO Find any referenced files!
|
// TODO Find any referenced files!
|
||||||
println!("cargo:rerun-if-changed=photos.scss");
|
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<String, String> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
21
src/main.rs
21
src/main.rs
@ -31,7 +31,7 @@ use diesel::prelude::*;
|
|||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use hyper::header::{Expires, HttpDate};
|
use hyper::header::{Expires, HttpDate};
|
||||||
use nickel::{FormBody, Halt, HttpRouter, MediaType, MiddlewareResult, Nickel,
|
use nickel::{FormBody, Halt, HttpRouter, MediaType, MiddlewareResult, Nickel,
|
||||||
Request, Response, StaticFilesHandler};
|
Request, Response};
|
||||||
use nickel::extensions::response::Redirect;
|
use nickel::extensions::response::Redirect;
|
||||||
use nickel::status::StatusCode;
|
use nickel::status::StatusCode;
|
||||||
use nickel_diesel::{DieselMiddleware, DieselRequestExtensions};
|
use nickel_diesel::{DieselMiddleware, DieselRequestExtensions};
|
||||||
@ -100,13 +100,10 @@ fn main() {
|
|||||||
|
|
||||||
let mut server = Nickel::new();
|
let mut server = Nickel::new();
|
||||||
server.utilize(RequestLoggerMiddleware);
|
server.utilize(RequestLoggerMiddleware);
|
||||||
|
server.get("/static/:file.:ext", wrap!(static_file: file, ext));
|
||||||
server.utilize(MemcacheMiddleware::new
|
server.utilize(MemcacheMiddleware::new
|
||||||
(vec![("tcp://127.0.0.1:11211".into(), 1)]));
|
(vec![("tcp://127.0.0.1:11211".into(), 1)]));
|
||||||
server.utilize(SessionMiddleware::new(&jwt_key()));
|
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<PgConnection> =
|
let dm: DieselMiddleware<PgConnection> =
|
||||||
DieselMiddleware::new(&dburl(), 5, Box::new(NopErrorHandler)).unwrap();
|
DieselMiddleware::new(&dburl(), 5, Box::new(NopErrorHandler)).unwrap();
|
||||||
server.utilize(dm);
|
server.utilize(dm);
|
||||||
@ -308,6 +305,20 @@ fn place_all<'mw>(req: &mut Request,
|
|||||||
query.order(place_name).load(c).expect("List places")))
|
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,
|
fn place_one<'mw>(req: &mut Request,
|
||||||
res: Response<'mw>,
|
res: Response<'mw>,
|
||||||
tslug: String)
|
tslug: String)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! Just fooling around with different ways to count images per year.
|
//! Just fooling around with different ways to count images per year.
|
||||||
extern crate rphotos;
|
extern crate rphotos;
|
||||||
|
extern crate brotli2;
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -7,6 +8,7 @@ extern crate diesel;
|
|||||||
extern crate djangohashers;
|
extern crate djangohashers;
|
||||||
extern crate dotenv;
|
extern crate dotenv;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
extern crate flate2;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate rexif;
|
extern crate rexif;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -18,7 +20,7 @@ mod adm;
|
|||||||
mod env;
|
mod env;
|
||||||
mod photosdir;
|
mod photosdir;
|
||||||
|
|
||||||
use adm::{findphotos, makepublic, readkpa, users};
|
use adm::{findphotos, makepublic, readkpa, users, storestatics};
|
||||||
use adm::result::Error;
|
use adm::result::Error;
|
||||||
use adm::stats::show_stats;
|
use adm::stats::show_stats;
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
@ -66,6 +68,11 @@ fn main() {
|
|||||||
.help("Image path to make public"))
|
.help("Image path to make public"))
|
||||||
.after_help("The image path(s) are relative to the \
|
.after_help("The image path(s) are relative to the \
|
||||||
image root."))
|
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();
|
.get_matches();
|
||||||
|
|
||||||
match run(args) {
|
match run(args) {
|
||||||
@ -122,6 +129,9 @@ fn run(args: ArgMatches) -> Result<(), Error> {
|
|||||||
("userpass", Some(args)) => {
|
("userpass", Some(args)) => {
|
||||||
users::passwd(&try!(get_db()), args.value_of("USER").unwrap())
|
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())),
|
_ => Ok(println!("No subcommand given.\n\n{}", args.usage())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
@use ::Link;
|
@use ::Link;
|
||||||
@use templates::head;
|
@use templates::head;
|
||||||
|
@use templates::statics::style_css;
|
||||||
|
|
||||||
@(title: &str, lpath: Vec<Link>, user: Option<String>, content: Content)
|
@(title: &str, lpath: Vec<Link>, user: Option<String>, content: Content)
|
||||||
|
|
||||||
@ -8,7 +9,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>@title</title>
|
<title>@title</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||||
@Html(include!(concat!(env!("OUT_DIR"), "/stylelink")))
|
<link rel="stylesheet" href="/static/@style_css.name" type="text/css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@:head(lpath, user)
|
@:head(lpath, user)
|
||||||
|
Loading…
Reference in New Issue
Block a user