Clean up error handling, logging and 404 correctly for static files

This commit is contained in:
Tristan Daniël Maat 2022-08-16 23:16:39 +01:00
parent 0f736978f3
commit d75acc8d05
Signed by: tlater
GPG key ID: 49670FD774E43268
3 changed files with 104 additions and 21 deletions

21
Cargo.lock generated
View file

@ -456,6 +456,19 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "env_logger"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "firestorm" name = "firestorm"
version = "0.5.1" version = "0.5.1"
@ -623,6 +636,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.3" version = "0.2.3"
@ -1196,7 +1215,9 @@ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",
"clap", "clap",
"env_logger",
"handlebars", "handlebars",
"log",
] ]
[[package]] [[package]]

View file

@ -7,4 +7,6 @@ edition = "2021"
actix-files = "0.6.2" actix-files = "0.6.2"
actix-web = "4.1.0" actix-web = "4.1.0"
clap = { version = "3.2.17", features = ["derive"] } clap = { version = "3.2.17", features = ["derive"] }
env_logger = "0.9.0"
handlebars = { version = "4.3.3", features = ["dir_source"] } handlebars = { version = "4.3.3", features = ["dir_source"] }
log = "0.4.17"

View file

@ -1,46 +1,106 @@
use std::io; use std::net::SocketAddr;
use actix_files::Files; use actix_files::NamedFile;
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder}; use actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, Responder};
use clap::Parser; use clap::Parser;
use handlebars::Handlebars; use handlebars::Handlebars;
use std::path::PathBuf;
#[derive(Parser, Debug)] #[derive(Parser, Debug, Clone)]
struct Config { struct Config {
template_directory: String, #[clap(long, value_parser)]
address: String, /// The directory from which to serve static content and
port: u16, /// handlebars templates
template_directory: PathBuf,
#[clap(long, default_value = "127.0.0.1:8000", value_parser)]
/// The address on which to listen
address: SocketAddr,
} }
async fn template(hb: web::Data<Handlebars<'_>>, req: HttpRequest) -> impl Responder { struct SharedData<'a> {
let path: String = req handlebars: Handlebars<'a>,
config: Config,
}
#[get("/{filename:.*.html}")]
async fn template(
shared: web::Data<SharedData<'_>>,
req: HttpRequest,
) -> Result<impl Responder, Box<dyn std::error::Error>> {
let path = req
.match_info() .match_info()
.query("filename") .query("filename")
.parse() .strip_suffix(".html")
.expect("need a file name on the request"); .expect("only paths with this suffix should get here");
let body = hb.render(path.strip_suffix(".html").unwrap(), &()).unwrap(); if shared.handlebars.has_template(path) {
let body = shared.handlebars.render(path, &())?;
Ok(HttpResponse::Ok().body(body))
} else {
let body = shared
.handlebars
.render("404", &())
.expect("404 template not found");
Ok(HttpResponse::NotFound().body(body))
}
}
HttpResponse::Ok().body(body) #[get("/{filename:.*}")]
async fn static_file(
shared: web::Data<SharedData<'_>>,
req: HttpRequest,
) -> Result<impl Responder, Box<dyn std::error::Error>> {
let requested = req.match_info().query("filename");
match shared
.config
.template_directory
.join(requested)
.canonicalize()
{
// We only want to serve paths that are both valid *and* in
// the template directory.
//
// i.e., don't serve up /etc/passwd
Ok(path) if path.starts_with(&shared.config.template_directory) => {
let file = NamedFile::open_async(path).await?;
Ok(file.use_last_modified(false).respond_to(&req))
}
// Any other cases should 404
_ => {
let body = shared
.handlebars
.render("404", &())
.expect("404 template not found");
Ok(HttpResponse::NotFound().body(body))
}
}
} }
#[actix_web::main] #[actix_web::main]
async fn main() -> io::Result<()> { async fn main() -> Result<(), std::io::Error> {
let config = Config::parse(); let mut config = Config::parse();
config.template_directory = config.template_directory.canonicalize()?;
env_logger::init();
let mut handlebars = Handlebars::new(); let mut handlebars = Handlebars::new();
handlebars handlebars
.register_templates_directory(".html", config.template_directory.clone()) .register_templates_directory(".html", config.template_directory.clone())
.expect("could not read template directory"); .expect("templates should compile correctly");
let handlebars_ref = web::Data::new(handlebars);
let shared_data = web::Data::new(SharedData {
handlebars,
config: config.clone(),
});
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.app_data(handlebars_ref.clone()) .app_data(shared_data.clone())
.route("/{filename:.*.html}", web::get().to(template)) .service(template)
.service(Files::new("/", config.template_directory.clone())) .service(static_file)
}) })
.bind((config.address, config.port))? .bind(config.address)?
.run() .run()
.await .await
} }