diff options
author | gennyble <gen@nyble.dev> | 2024-02-22 04:12:40 -0600 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2024-02-22 04:12:40 -0600 |
commit | 23876c5420c20292966367659708a200c8668f96 (patch) | |
tree | 0c8b0fd06d622cb16511a6e7610511986a9d9d31 /src/main.rs | |
parent | c4eff133ed2a2c3deb8fad322a430b3263b6e6ab (diff) | |
download | awake-23876c5420c20292966367659708a200c8668f96.tar.gz awake-23876c5420c20292966367659708a200c8668f96.zip |
Minimum viable
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 150 |
1 files changed, 149 insertions, 1 deletions
diff --git a/src/main.rs b/src/main.rs index 1ba94f7..c429e8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,151 @@ +mod error; mod fs; +mod settings; +mod templated; -fn main() {} +use std::{os::unix::fs::MetadataExt, str::FromStr}; + +use axum::{ + body::Body, extract::Path, http::header, response::Response, routing::get, Extension, Router, +}; +use bempline::{Document, Options}; +use camino::Utf8PathBuf; +pub use error::RuntimeError; +use fs::Filesystem; +use settings::Settings; +use tokio_util::io::ReaderStream; + +use crate::templated::Templated; + +#[tokio::main] +async fn main() { + let fs = Filesystem::new("../inf/served"); + + let settings = Settings { + template_dir: Utf8PathBuf::from("../inf/templates"), + }; + + let app = Router::new() + .route("/", get(index_handler)) + .route("/*path", get(handler)) + .layer(Extension(fs)) + .layer(Extension(settings)); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:2560").await.unwrap(); + axum::serve(listener, app).await.unwrap() +} + +async fn index_handler(fse: Extension<Filesystem>, se: Extension<Settings>) -> Response { + handler(fse, se, Path(String::from("/"))).await +} + +async fn handler( + Extension(fs): Extension<Filesystem>, + Extension(settings): Extension<Settings>, + Path(path): Path<String>, +) -> Response { + match falible_handler(fs, settings, path).await { + Ok(resp) => resp, + Err(re) => Response::builder() + .body(Body::from(re.to_string())) + .unwrap(), + } +} + +async fn falible_handler( + fs: Filesystem, + settings: Settings, + path: String, +) -> Result<Response, RuntimeError> { + println!("raw = {path}"); + + let path = path.parse()?; + + println!("path = {path}"); + + let filepath = fs.resolve(&path)?; + + let ext = filepath.extension().unwrap_or_default(); + + if ext != "html" { + send_file(filepath).await + } else { + let content = Filesystem::read_to_string(&filepath).await?; + match Templated::from_str(&content) { + Ok(templated) => send_template(templated, filepath, settings).await, + Err(_) => Ok(Response::builder() + .header(header::CONTENT_TYPE, "text/html") + .body(Body::from(content)) + .unwrap()), + } + } +} + +// 20 megabytes +const STREAM_AFTER: u64 = 20 * 1024 * 1024; + +async fn send_file(filepath: Utf8PathBuf) -> Result<Response, RuntimeError> { + let ext = filepath.extension().unwrap_or_default(); + + let mime = match ext { + // Text + "css" => "text/css", + "html" => "text/html", + "js" => "text/javascript", + "txt" => "txt/plain", + + // Multimedia + "gif" => "image/gif", + "jpg" | "jpeg" => "image/jpeg", + "mp4" => "video/mp4", + "png" => "image/png", + _ => "", + }; + + let mut response = Response::builder(); + if !mime.is_empty() { + response = response.header(header::CONTENT_TYPE, mime); + } + + let metadata = Filesystem::metadata(&filepath)?; + if metadata.size() > STREAM_AFTER { + let file = Filesystem::open(filepath).await?; + let stream = ReaderStream::new(file); + Ok(response.body(Body::from_stream(stream)).unwrap()) + } else { + let content = Filesystem::read(filepath).await?; + Ok(response.body(Body::from(content)).unwrap()) + } +} + +async fn send_template( + templated: Templated, + path: Utf8PathBuf, + settings: Settings, +) -> Result<Response, RuntimeError> { + let template_stem = templated.frontmatter.get("template").expect("no template"); + let template_name = Utf8PathBuf::from(format!("{template_stem}.html")); + let template_path = settings.template_dir.join(template_name); + + let filename = path.file_name().expect("template has no filename"); + + let mut template = Document::from_file( + template_path, + Options::default().include_path(bempline::options::IncludeMethod::Path( + settings.template_dir.as_std_path().to_owned(), + )), + ) + .unwrap(); + + template.set( + "title", + templated.frontmatter.get("title").unwrap_or(filename), + ); + + template.set("main", templated.content); + + Ok(Response::builder() + .header(header::CONTENT_TYPE, "text/html") + .body(Body::from(template.compile())) + .unwrap()) +} |