Clean up error handling, logging and 404 correctly for static files
This commit is contained in:
parent
0f736978f3
commit
d75acc8d05
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -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]]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
102
src/main.rs
102
src/main.rs
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue