From 0198b808282f86796c0820b7196c5afbbf0fe8b4 Mon Sep 17 00:00:00 2001 From: Logan Kaser Date: Fri, 16 Feb 2024 21:11:20 -0800 Subject: [PATCH 1/3] fmt and clippy --- src/cli/interface.rs | 12 ++-- src/cli/messages.rs | 7 +- src/cli/mod.rs | 2 +- src/core/config.rs | 38 +++++----- src/core/engine.rs | 40 +++++------ src/core/files.rs | 162 +++++++++++++++++++++--------------------- src/core/mod.rs | 8 +-- src/core/routes.rs | 68 ++++++++---------- src/core/server.rs | 142 +++++++++++++++++------------------- src/core/templates.rs | 17 +++-- src/core/tls.rs | 24 +++---- src/core/watcher.rs | 53 +++++++------- 12 files changed, 272 insertions(+), 301 deletions(-) diff --git a/src/cli/interface.rs b/src/cli/interface.rs index 0aff044..46adbb8 100644 --- a/src/cli/interface.rs +++ b/src/cli/interface.rs @@ -1,12 +1,10 @@ -use clap::{Command, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command}; use crate::core::VERSION; /// Prints an ASCII art banner to look cool! pub fn banner() { - eprintln!( - "{}", format!("{} {}\n", include_str!("banner"), VERSION) - ) + eprintln!("{} {}\n", include_str!("banner"), VERSION) } /// Command-line arguments @@ -33,13 +31,13 @@ pub fn args() -> ArgMatches { .value_name("TLS KEY") .help("TLS key file.") .required(false) - .takes_value(true)) + .takes_value(true)) .arg(Arg::new("tls_cert") .short('c') .long("cert") .value_name("TLS CERT") .help("TLS cert file.") .required(false) - .takes_value(true)) + .takes_value(true)) .get_matches() -} \ No newline at end of file +} diff --git a/src/cli/messages.rs b/src/cli/messages.rs index fc46154..ba2695c 100644 --- a/src/cli/messages.rs +++ b/src/cli/messages.rs @@ -17,8 +17,7 @@ pub fn push_message(log_type: Type, message: &str) { Type::_Skipped => format!("{}{}{}", "[".bold(), "SKIPPED".bold().yellow(), "]".bold()), Type::Error => format!("{}{}{}", "[".bold(), "ERROR".bold().red(), "]".bold()), Type::Info => format!("{}{}{}", "[".bold(), "INFO".bold().cyan(), "]".bold()), - Type::Success => format!("{}{}{}", "[".bold(), "SUCCESS".bold().green(), "]".bold()) + Type::Success => format!("{}{}{}", "[".bold(), "SUCCESS".bold().green(), "]".bold()), }; - - eprintln!("{}", format!("{} {}", prefix, message)) -} \ No newline at end of file + eprintln!("{} {}", prefix, message) +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 0e5cb40..5c2f6ed 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,2 +1,2 @@ pub(crate) mod interface; -pub(crate) mod messages; \ No newline at end of file +pub(crate) mod messages; diff --git a/src/core/config.rs b/src/core/config.rs index d1d2533..b441816 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,20 +1,19 @@ use std::collections::HashMap; -use std::path::{Path, PathBuf}; use std::fs::File; use std::io::BufReader; use std::io::{self, prelude::*}; +use std::path::{Path, PathBuf}; -use serde::{Serialize, Deserialize}; -use serde_json; +use serde::{Deserialize, Serialize}; -pub const CONFIG_FILE: &'static str = "binserve.json"; +pub const CONFIG_FILE: &str = "binserve.json"; #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct Tls { pub host: String, pub enable: bool, - + #[serde(default)] pub key: PathBuf, @@ -27,7 +26,7 @@ pub struct Server { pub host: String, #[serde(default)] - pub tls: Tls + pub tls: Tls, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -39,7 +38,7 @@ pub struct Static { pub served_from: String, #[serde(default)] - pub error_pages: HashMap + pub error_pages: HashMap, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -48,12 +47,16 @@ pub struct Template { pub partials: HashMap, #[serde(default)] - pub variables: HashMap + pub variables: HashMap, } // configuration toggles -const fn enabled() -> bool { true } -const fn disabled() -> bool { false } +const fn enabled() -> bool { + true +} +const fn disabled() -> bool { + false +} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { @@ -76,7 +79,7 @@ pub struct Config { pub follow_symlinks: bool, #[serde(default = "disabled")] - pub enable_logging: bool + pub enable_logging: bool, } /// secure/fallback defaults @@ -89,7 +92,7 @@ impl Default for Config { enable_directory_listing: false, minify_html: false, follow_symlinks: false, - enable_logging: false + enable_logging: false, } } } @@ -109,16 +112,15 @@ pub struct BinserveConfig { pub config: Config, #[serde(default)] - pub insert_headers: HashMap + pub insert_headers: HashMap, } use once_cell::sync::Lazy; use parking_lot::Mutex; /// A universal config state -pub static CONFIG_STATE: Lazy> = Lazy::new(|| { - Mutex::new(BinserveConfig::default()) -}); +pub static CONFIG_STATE: Lazy> = + Lazy::new(|| Mutex::new(BinserveConfig::default())); impl BinserveConfig { /// Read and serialize the config file. @@ -126,7 +128,7 @@ impl BinserveConfig { let config_file = File::open(CONFIG_FILE)?; let buf_reader = BufReader::new(config_file); let config: BinserveConfig = serde_json::from_reader(buf_reader)?; - + // update global config state *CONFIG_STATE.lock() = config.to_owned(); @@ -145,4 +147,4 @@ impl BinserveConfig { Ok(()) } -} \ No newline at end of file +} diff --git a/src/core/engine.rs b/src/core/engine.rs index 93780a3..dc99951 100644 --- a/src/core/engine.rs +++ b/src/core/engine.rs @@ -1,12 +1,8 @@ use crate::cli::interface; -use super::{ - files, server, templates, watcher, - routes::RouteHandle, - config::BinserveConfig, -}; +use super::{config::BinserveConfig, files, routes::RouteHandle, server, templates, watcher}; -use crate::cli::messages::{Type, push_message}; +use crate::cli::messages::{push_message, Type}; pub fn init() -> anyhow::Result<()> { let start_time = std::time::Instant::now(); @@ -22,9 +18,15 @@ pub fn init() -> anyhow::Result<()> { // override with cli configurations if any let cli_args = interface::args(); - cli_args.value_of("host").map(|host| config.server.host = host.into()); - cli_args.value_of("tls_key").map(|tls_key| config.server.tls.key = tls_key.into()); - cli_args.value_of("tls_cert").map(|tls_cert| config.server.tls.key = tls_cert.into()); + if let Some(host) = cli_args.value_of("host") { + config.server.host = host.into(); + } + if let Some(tls_key) = cli_args.value_of("tls_key") { + config.server.tls.key = tls_key.into(); + } + if let Some(tls_cert) = cli_args.value_of("tls_cert") { + config.server.tls.key = tls_cert.into(); + } // prepare template partials let handlebars_handle = templates::render_templates(&config)?; @@ -37,36 +39,28 @@ pub fn init() -> anyhow::Result<()> { if end_time.as_millis() == 0 { push_message( Type::Info, - &format!("Build finished in {} μs ⚡", end_time.as_micros()) + &format!("Build finished in {} μs ⚡", end_time.as_micros()), ) } else { push_message( Type::Info, - &format!("Build finished in {} ms ⚡", end_time.as_millis()) + &format!("Build finished in {} ms ⚡", end_time.as_millis()), ) } if config.server.tls.enable { - push_message( - Type::Info, - "Enabled TLS (HTTPS) 🔒" - ) + push_message(Type::Info, "Enabled TLS (HTTPS) 🔒") } if config.config.enable_logging { - push_message( - Type::Info, - "Enabled logging 📜" - ) + push_message(Type::Info, "Enabled logging 📜") } // start the hot reloader (file wacther) - std::thread::spawn(|| { - watcher::hot_reload_files() - }); + std::thread::spawn(watcher::hot_reload_files); // and finally server take off! server::run_server(config)?; Ok(()) -} \ No newline at end of file +} diff --git a/src/core/files.rs b/src/core/files.rs index 2270b36..d305652 100644 --- a/src/core/files.rs +++ b/src/core/files.rs @@ -1,18 +1,18 @@ use actix_web::{ + http::header::{HeaderValue, HttpDate}, web::Bytes, - http::header::{HttpDate, HeaderValue} }; -use std::path::{Path, PathBuf}; +use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, prelude::*}; -use std::collections::HashMap; +use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; use etag::EntityTag; -use handlebars::{Handlebars, Context as HbsContext}; +use handlebars::{Context as HbsContext, Handlebars}; use minify_html_onepass::Cfg; @@ -21,12 +21,12 @@ use super::config::{CONFIG_FILE, CONFIG_STATE}; /// Represents a static file #[derive(Debug)] pub struct StaticFile { - pub mime: Option, // mime type of the file - pub bytes: Bytes, // contents of the file in bytes - pub path: PathBuf, // path to the file in disk - pub etag: Option, // etag header value (RFC 7232 §2.3) + pub mime: Option, // mime type of the file + pub bytes: Bytes, // contents of the file in bytes + pub path: PathBuf, // path to the file in disk + pub etag: Option, // etag header value (RFC 7232 §2.3) pub last_modified: Option, // last modified system time (RFC 7232 §2.2) - pub hbs_bytes: Bytes // to read pre-rendered handlebars content + pub hbs_bytes: Bytes, // to read pre-rendered handlebars content } /// Max file size allowed to be cached in memory @@ -34,10 +34,7 @@ const MAX_FILE_SIZE: u64 = 104_857_600; impl StaticFile { /// Creates a static file instance - pub fn create( - path: &PathBuf, - handlebars_handle: &(Handlebars, HbsContext) - ) -> Result { + pub fn create(path: &PathBuf, handlebars_handle: &(Handlebars, HbsContext)) -> Result { // read the current config state let config_state = &*CONFIG_STATE.lock(); @@ -54,16 +51,14 @@ impl StaticFile { // if configured not to follow symlinks if file_metadata.is_symlink() && !config_state.config.follow_symlinks { - return Ok( - Self { - mime: None, - bytes: Bytes::new(), - path: path.to_path_buf(), - etag: None, - last_modified: None, - hbs_bytes: Bytes::new() - } - ) + return Ok(Self { + mime: None, + bytes: Bytes::new(), + path: path.to_path_buf(), + etag: None, + last_modified: None, + hbs_bytes: Bytes::new(), + }); } // derive an etag from the file's metadata @@ -74,7 +69,8 @@ impl StaticFile { // get the mime type of the file // by default the mime type falls to `application/octet-stream` - let mut mime_type = new_mime_guess::from_path(path).first_raw() + let mut mime_type = new_mime_guess::from_path(path) + .first_raw() .unwrap_or("application/octet-stream"); // render handlebars templates (.hbs templates) @@ -83,8 +79,7 @@ impl StaticFile { let ext = path.extension(); if ext.is_some() { - let extension: &str = ext.unwrap().to_str() - .unwrap(); + let extension: &str = ext.unwrap().to_str().unwrap(); // identify handlebars template if extension == "hbs" { @@ -95,20 +90,18 @@ impl StaticFile { // render the template contents = Bytes::from(hbs_reg.render_template_with_context( - &String::from_utf8_lossy(&contents[..]), &hbs_ctx)? - ); - + &String::from_utf8_lossy(&contents[..]), + hbs_ctx, + )?); + hbs_prerendered_bytes = contents.to_owned(); } } // minify html if configured if mime_type == "text/html" && config_state.config.minify_html { - match minify_html_onepass::copy(&contents, &Cfg::new()) { - Ok(minified_html) => { - contents = Bytes::from(minified_html); - } - _ => () + if let Ok(minified_html) = minify_html_onepass::copy(&contents, &Cfg::new()) { + contents = Bytes::from(minified_html); } } @@ -125,34 +118,30 @@ impl StaticFile { // when it's a website with thousands of static files in which case // one should totally disable in-memory caching of files unless you // have a chonker of a RAM. - // + // // TODO: How about caching frequently accessed files? Like a priority cache? // // It skips this whole step if the `fast_mem_cache` feature is disabled. if file_size < MAX_FILE_SIZE && config_state.config.fast_mem_cache { - return Ok( - Self { - mime, - bytes: contents, - path: path.to_path_buf(), - etag, - last_modified, - hbs_bytes: Bytes::new() - } - ) - } - - // fallbacks to file from disk by default - Ok( - Self { + return Ok(Self { mime, - bytes: Bytes::new(), + bytes: contents, path: path.to_path_buf(), etag, last_modified, - hbs_bytes: hbs_prerendered_bytes - } - ) + hbs_bytes: Bytes::new(), + }); + } + + // fallbacks to file from disk by default + Ok(Self { + mime, + bytes: Bytes::new(), + path: path.to_path_buf(), + etag, + last_modified, + hbs_bytes: hbs_prerendered_bytes, + }) } } @@ -168,26 +157,22 @@ pub fn generate_not_found() -> Result { // for defined error page templates if !error_page_map.is_empty() { - match error_page_map.get(&404) { - Some(template_path) => { - let read_file = fs::read(template_path) - .with_context(|| format!("Failed to read file {:?}", template_path.to_string_lossy()))?; - not_found_template = Bytes::from(read_file) - }, - None => () + if let Some(template_path) = error_page_map.get(&404) { + let read_file = fs::read(template_path).with_context(|| { + format!("Failed to read file {:?}", template_path.to_string_lossy()) + })?; + not_found_template = Bytes::from(read_file) } } - Ok( - StaticFile { - mime: None, - bytes: not_found_template, - path: PathBuf::new(), - etag: None, - last_modified: None, - hbs_bytes: Bytes::new() - } - ) + Ok(StaticFile { + mime: None, + bytes: not_found_template, + path: PathBuf::new(), + etag: None, + last_modified: None, + hbs_bytes: Bytes::new(), + }) } /// Generate the starter boilerplate. @@ -198,14 +183,31 @@ pub fn generate_starter_boilerplate() -> io::Result<()> { // contain the boilerplate starter code in binary let starter_directory: HashMap> = HashMap::from([ // public root directory - ("public/index.html".into(), include_bytes!("../starter/index.html").to_vec()), - ("public/404.html".into(), include_bytes!("../starter/404.html").to_vec()), - ("public/usage.hbs".into(), include_bytes!("../starter/usage.hbs").to_vec()), - ("public/header.hbs".into(), include_bytes!("../starter/header.hbs").to_vec()), - + ( + "public/index.html".into(), + include_bytes!("../starter/index.html").to_vec(), + ), + ( + "public/404.html".into(), + include_bytes!("../starter/404.html").to_vec(), + ), + ( + "public/usage.hbs".into(), + include_bytes!("../starter/usage.hbs").to_vec(), + ), + ( + "public/header.hbs".into(), + include_bytes!("../starter/header.hbs").to_vec(), + ), // assets directory - ("public/assets/css/styles.css".into(), include_bytes!("../starter/assets/css/styles.css").to_vec()), - ("public/assets/images/binserve.webp".into(), include_bytes!("../starter/assets/images/binserve.webp").to_vec()) + ( + "public/assets/css/styles.css".into(), + include_bytes!("../starter/assets/css/styles.css").to_vec(), + ), + ( + "public/assets/images/binserve.webp".into(), + include_bytes!("../starter/assets/images/binserve.webp").to_vec(), + ), ]); // prepare the default public directories @@ -218,8 +220,8 @@ pub fn generate_starter_boilerplate() -> io::Result<()> { // write the static files to disk for (path, contents) in starter_directory { let mut file = File::create(path)?; - file.write_all(&contents)?; - } + file.write_all(&contents)?; + } } Ok(()) diff --git a/src/core/mod.rs b/src/core/mod.rs index 2bf9a25..eb4fedc 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,10 +1,10 @@ -pub(crate) mod engine; pub(super) mod config; -pub(super) mod routes; +pub(crate) mod engine; pub(super) mod files; +pub(super) mod routes; pub(super) mod server; pub(super) mod templates; -pub(super) mod watcher; pub(super) mod tls; +pub(super) mod watcher; -pub static VERSION: &'static str = "0.2.0"; \ No newline at end of file +pub static VERSION: &str = "0.2.0"; diff --git a/src/core/routes.rs b/src/core/routes.rs index b90f6a9..55a2e4a 100644 --- a/src/core/routes.rs +++ b/src/core/routes.rs @@ -1,30 +1,30 @@ -use once_cell::sync::Lazy; use dashmap::DashMap; +use once_cell::sync::Lazy; /// routes are usually small in size, store them in the stack use compact_str::CompactString; -use std::path::PathBuf; use std::collections::HashMap; +use std::path::PathBuf; // multi-threaded directory walking -use jwalk::{WalkDir}; +use jwalk::WalkDir; /// Route type indicating whether the file is read from memory or disk #[derive(Debug, PartialEq)] pub enum Type { Bytes, - File + File, } /// Represents a static file, both in-memory and from disk -use super::files::{StaticFile, generate_not_found}; +use super::files::{generate_not_found, StaticFile}; /// Struct to contain and handle the Response type for the route. #[derive(Debug)] pub struct RouteHandle { pub r#type: Type, - pub response: StaticFile + pub response: StaticFile, } /// Use ahash as the hasher for the concurrent hashmap @@ -32,17 +32,16 @@ use ahash::RandomState; /// A concurrent HashMap containing all the routes and the bytes to it's corresponding files. /// Files are read at initialization so as to prevent I/O operations at runtime (only when `fast_mem_cache` is enabled) -pub static ROUTEMAP: Lazy> = Lazy::new(|| { - DashMap::with_hasher(RandomState::new()) -}); +pub static ROUTEMAP: Lazy> = + Lazy::new(|| DashMap::with_hasher(RandomState::new())); /// Manages routes and it's corresponding responses impl RouteHandle { /// Add routes to the concurrent hashmap containing the routes. pub fn add_routes( route_set: &HashMap, - handlebars_handle: &(handlebars::Handlebars, handlebars::Context) - ) -> anyhow::Result<()> { + handlebars_handle: &(handlebars::Handlebars, handlebars::Context), + ) -> anyhow::Result<()> { for (route, path) in route_set { if path.is_dir() { // create a route entry for each file where the file path @@ -60,11 +59,13 @@ impl RouteHandle { // a file under the `posts` directory for example // `/public/posts/how-to-comfort-the-borrow-checker/read.html` // will be resolved to the route and accessible by - // `https://www.rustevangelismstrikeforce.com/how-to-comfort-the-borrow-checker/read.html`. + // `https://www.rustevangelismstrikeforce.com/how-to-comfort-the-borrow-checker/read.html`. let entry = entry?; if entry.file_type().is_file() { - let mut route_index: String = entry.path().to_string_lossy() + let mut route_index: String = entry + .path() + .to_string_lossy() .replace(&starting_directory, ""); // combine route definition and file path under the specified directory @@ -82,16 +83,12 @@ impl RouteHandle { Self::associate_files_to_routes( &route_index, &entry.path(), - &handlebars_handle + handlebars_handle, )? } } } else { - Self::associate_files_to_routes( - route, - path, - &handlebars_handle - )? + Self::associate_files_to_routes(route, path, handlebars_handle)? } } @@ -107,13 +104,10 @@ impl RouteHandle { let route_handle = RouteHandle { r#type: Type::Bytes, - response: not_found_page + response: not_found_page, }; - ROUTEMAP.insert( - "{{404}}".into(), - route_handle - ); + ROUTEMAP.insert("{{404}}".into(), route_handle); Ok(()) } @@ -122,24 +116,23 @@ impl RouteHandle { pub fn associate_files_to_routes( route: &String, path: &PathBuf, - handlebars_handle: &(handlebars::Handlebars, handlebars::Context) + handlebars_handle: &(handlebars::Handlebars, handlebars::Context), ) -> anyhow::Result<()> { // create a static file instance containing it's mime type, contents, and metadata - let static_file = StaticFile::create(&path, &handlebars_handle)?; + let static_file = StaticFile::create(path, handlebars_handle)?; - let route_handle: RouteHandle; - - if static_file.bytes.is_empty() { // this means the file is not in-memory - route_handle = RouteHandle { + let route_handle = if static_file.bytes.is_empty() { + // this means the file is not in-memory + RouteHandle { r#type: Type::File, - response: static_file + response: static_file, } } else { - route_handle = RouteHandle { + RouteHandle { r#type: Type::Bytes, - response: static_file + response: static_file, } - } + }; // pop the initial trailing slash if it exists let mut route_fmt = route.chars(); @@ -153,11 +146,8 @@ impl RouteHandle { route_str = format!("/{}", route_str); - ROUTEMAP.insert( - route_str.into(), - route_handle - ); + ROUTEMAP.insert(route_str.into(), route_handle); Ok(()) } -} \ No newline at end of file +} diff --git a/src/core/server.rs b/src/core/server.rs index c51a433..234f15f 100644 --- a/src/core/server.rs +++ b/src/core/server.rs @@ -1,14 +1,13 @@ use actix_web::{ - web, Result, - middleware::{self, Logger, Compress, Condition}, http::{ - KeepAlive, header::{ - ETAG, CACHE_CONTROL, SERVER, IF_MODIFIED_SINCE, IF_NONE_MATCH, LAST_MODIFIED, - HeaderMap, HeaderValue - } + HeaderMap, HeaderValue, CACHE_CONTROL, ETAG, IF_MODIFIED_SINCE, IF_NONE_MATCH, + LAST_MODIFIED, SERVER, + }, + KeepAlive, }, - App, HttpServer, HttpRequest, HttpResponse + middleware::{self, Compress, Condition, Logger}, + web, App, HttpRequest, HttpResponse, HttpServer, Result, }; use actix_files::{self, NamedFile}; @@ -17,13 +16,12 @@ use actix_web_lab::middleware::RedirectHttps; use std::path::{Path, PathBuf}; use super::{ - VERSION, - routes::{ROUTEMAP, Type}, - config::{CONFIG_STATE, BinserveConfig}, - tls + config::{BinserveConfig, CONFIG_STATE}, + routes::{Type, ROUTEMAP}, + tls, VERSION, }; -use crate::cli::messages::{Type as MsgType, push_message}; +use crate::cli::messages::{push_message, Type as MsgType}; /// Check for `If-None-Matched` and `If-Modified-Since` headers /// for enabling browser caching. @@ -32,7 +30,7 @@ use crate::cli::messages::{Type as MsgType, push_message}; async fn request_client_is_cached( headers: &HeaderMap, etag: &HeaderValue, - last_modified: &HeaderValue + last_modified: &HeaderValue, ) -> bool { match (headers.get(IF_NONE_MATCH), headers.get(IF_MODIFIED_SINCE)) { (None, None) => return false, @@ -40,21 +38,21 @@ async fn request_client_is_cached( // if both of them match the requested file's state, // return `304` to respond with the browser cache if req_etag == etag && req_last_modified == last_modified { - return true + return true; } - }, + } (Some(req_etag), None) => { // if the etag value match the requested file's state, // return `304` to respond with the browser cache if req_etag == etag { - return true + return true; } - }, + } (None, Some(req_last_modified)) => { // if the last modified system time match the requested file's state, // return `304` to respond with the browser cache if req_last_modified == last_modified { - return true + return true; } } } @@ -87,42 +85,38 @@ async fn router(req: HttpRequest) -> Result { let headers = req.headers(); // if the request client is cached, respond with the cache content (304 Not Modified) - if request_client_is_cached(&headers, &etag, &last_modified).await { + if request_client_is_cached(headers, etag, last_modified).await { // 304 Not Modified return Ok(HttpResponse::NotModified() .insert_header((LAST_MODIFIED, last_modified)) .insert_header((ETAG, etag)) - .finish()) + .finish()); } - return Ok( - HttpResponse::Ok() - .insert_header((LAST_MODIFIED, last_modified)) - .insert_header((ETAG, etag)) - .content_type(mime_type) - .body(handler.response.hbs_bytes.to_owned()) - ) + return Ok(HttpResponse::Ok() + .insert_header((LAST_MODIFIED, last_modified)) + .insert_header((ETAG, etag)) + .content_type(mime_type) + .body(handler.response.hbs_bytes.to_owned())); } - return Ok( - NamedFile::open(handler.response.path.to_owned())? - .prefer_utf8(true) - .use_etag(true) - .use_last_modified(true) - .into_response(&req) - ) + return Ok(NamedFile::open(&handler.response.path)? + .prefer_utf8(true) + .use_etag(true) + .use_last_modified(true) + .into_response(&req)); } // get the request headers let headers = req.headers(); // if the request client is cached, respond with the cache content (304 Not Modified) - if request_client_is_cached(&headers, &etag, &last_modified).await { + if request_client_is_cached(headers, etag, last_modified).await { // 304 Not Modified return Ok(HttpResponse::NotModified() .insert_header((LAST_MODIFIED, last_modified)) .insert_header((ETAG, etag)) - .finish()) + .finish()); } // fallback to returning the current state by default @@ -131,7 +125,7 @@ async fn router(req: HttpRequest) -> Result { .insert_header((ETAG, etag)) .content_type(mime_type) .body(body)) // NOTE: should we `stream` body here, testing didn't show much changes? - }, + } None => { let not_found_handler = ROUTEMAP.get("{{404}}").unwrap(); let handle = not_found_handler.value(); @@ -152,13 +146,10 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { // enable logging middleware if config_state.config.enable_logging { - env_logger::try_init_from_env( - env_logger::Env::new().default_filter_or("info") - ).unwrap_or_default(); + env_logger::try_init_from_env(env_logger::Env::new().default_filter_or("info")) + .unwrap_or_default(); - logger = Logger::new( - "%a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T" - ); + logger = Logger::new("%a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T"); } logger @@ -167,7 +158,8 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { let mut headers_middleware = middleware::DefaultHeaders::new(); // binserve server header - headers_middleware = headers_middleware.add((SERVER, format!("binserve/{}", VERSION))); + headers_middleware = + headers_middleware.add((SERVER, format!("binserve/{}", VERSION))); // Add the `Cache-Control` header if enabled in config. // @@ -180,9 +172,8 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { // overwrite specified headers if !config_state.insert_headers.is_empty() { for (header, value) in config_state.insert_headers.iter() { - headers_middleware = headers_middleware.add( - (header.as_str(), value.as_str()) - ); + headers_middleware = + headers_middleware.add((header.as_str(), value.as_str())); } } @@ -192,9 +183,8 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { // enable TLS autoredirect to HTTPs .wrap({ // enable default redirect - let mut middleware = Condition::new( - config_state.server.tls.enable, RedirectHttps::default() - ); + let mut middleware = + Condition::new(config_state.server.tls.enable, RedirectHttps::default()); // if it's a port like 8443, resolve to that instead let tls_host = &config_state.server.tls.host; @@ -205,14 +195,14 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { let tls_address = tls_host.split(':'); let tls_address = tls_address.collect::>(); let mut tls_port = "443"; - if tls_address.len() > 1 { tls_port = tls_address[1] } + if tls_address.len() > 1 { + tls_port = tls_address[1] + } if tls_port != "443" { middleware = Condition::new( config_state.server.tls.enable, - RedirectHttps::default().to_port( - tls_port.parse::().unwrap() - ) + RedirectHttps::default().to_port(tls_port.parse::().unwrap()), ); } @@ -224,28 +214,26 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { if !static_served_from.is_empty() && static_directory != &PathBuf::new() { app_instance = app_instance.service({ - let mut static_file_service = actix_files::Files::new( - static_served_from, - static_directory - ) - // don't follow symlinks unless explicitly stated otherwise - .path_filter(|path, _| { - let config_state = &*CONFIG_STATE.lock(); - - // if configured to follow symlinks - if config_state.config.follow_symlinks { - false - } else { - Path::new(&config_state.r#static.directory) - .join(path) - .symlink_metadata() - .map(|m| !m.file_type().is_symlink()) - .unwrap_or(false) - } - }) - .prefer_utf8(true) - .use_etag(true) - .use_last_modified(true); + let mut static_file_service = + actix_files::Files::new(static_served_from, static_directory) + // don't follow symlinks unless explicitly stated otherwise + .path_filter(|path, _| { + let config_state = &*CONFIG_STATE.lock(); + + // if configured to follow symlinks + if config_state.config.follow_symlinks { + false + } else { + Path::new(&config_state.r#static.directory) + .join(path) + .symlink_metadata() + .map(|m| !m.file_type().is_symlink()) + .unwrap_or(false) + } + }) + .prefer_utf8(true) + .use_etag(true) + .use_last_modified(true); // if configured to allow directory listing or not // for the static files. @@ -271,7 +259,7 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { push_message( MsgType::Success, - &format!("Your server is up and running at {} 🚀", host) + &format!("Your server is up and running at {} 🚀", host), ); host diff --git a/src/core/templates.rs b/src/core/templates.rs index a271b9e..e78f63f 100644 --- a/src/core/templates.rs +++ b/src/core/templates.rs @@ -1,12 +1,11 @@ -use handlebars::{Handlebars, Context as HbsContext}; +use handlebars::{Context as HbsContext, Handlebars}; use anyhow::{Context, Result}; use super::config::BinserveConfig; /// Prepare the partials and template variables for handlebars at initialization. -pub fn render_templates(config: &BinserveConfig) --> Result<(Handlebars<'static>, HbsContext)> { +pub fn render_templates(config: &BinserveConfig) -> Result<(Handlebars<'static>, HbsContext)> { let mut handlebars_reg = Handlebars::new(); // register the context with the template variables @@ -15,11 +14,15 @@ pub fn render_templates(config: &BinserveConfig) // prepare template partials for (partial_name, template_path) in &config.template.partials { // register the partial templates - let partial_template = std::fs::read_to_string(template_path) - .with_context(|| format!("Failed to read Handlebars partial file: {:?}", template_path))?; + let partial_template = std::fs::read_to_string(template_path).with_context(|| { + format!( + "Failed to read Handlebars partial file: {:?}", + template_path + ) + })?; - handlebars_reg.register_partial(&partial_name, partial_template)?; + handlebars_reg.register_partial(partial_name, partial_template)?; } Ok((handlebars_reg, hbs_context)) -} \ No newline at end of file +} diff --git a/src/core/tls.rs b/src/core/tls.rs index a405c65..1b5697c 100644 --- a/src/core/tls.rs +++ b/src/core/tls.rs @@ -8,7 +8,7 @@ use anyhow::{Context, Result}; use super::config::CONFIG_STATE; -use crate::cli::messages::{Type, push_message}; +use crate::cli::messages::{push_message, Type}; /// Load TLS configuration pub fn load_rustls_config() -> Result { @@ -23,15 +23,15 @@ pub fn load_rustls_config() -> Result { let cert_key_path = &config_state.server.tls.key; // load TLS key/cert files - let cert_file = &mut BufReader::new( - File::open(cert_file_path) - .with_context(|| format!("Failed to read file {:?}", cert_file_path.to_string_lossy()))? - ); + let cert_file = + &mut BufReader::new(File::open(cert_file_path).with_context(|| { + format!("Failed to read file {:?}", cert_file_path.to_string_lossy()) + })?); - let key_file = &mut BufReader::new( - File::open(cert_key_path) - .with_context(|| format!("Failed to read file {:?}", cert_key_path.to_string_lossy()))? - ); + let key_file = + &mut BufReader::new(File::open(cert_key_path).with_context(|| { + format!("Failed to read file {:?}", cert_key_path.to_string_lossy()) + })?); // convert files to key/cert objects let cert_chain = certs(cert_file) @@ -52,7 +52,5 @@ pub fn load_rustls_config() -> Result { std::process::exit(1); } - Ok( - config.with_single_cert(cert_chain, keys.remove(0))? - ) -} \ No newline at end of file + Ok(config.with_single_cert(cert_chain, keys.remove(0))?) +} diff --git a/src/core/watcher.rs b/src/core/watcher.rs index 31b8408..daa26b6 100644 --- a/src/core/watcher.rs +++ b/src/core/watcher.rs @@ -2,14 +2,14 @@ use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use compact_str::CompactString; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; -use std::time::Duration; use std::sync::mpsc::channel; -use std::collections::HashMap; +use std::time::Duration; +use super::config::{BinserveConfig, CONFIG_FILE}; use super::routes::{RouteHandle, Type, ROUTEMAP}; -use super::config::{CONFIG_FILE, BinserveConfig}; use super::templates; /// Watch for filesystem for updates/writes and hot reload the server state. @@ -18,7 +18,7 @@ pub fn hot_reload_files() -> anyhow::Result<()> { // check if hot reload is enabled or not if !config_state.config.enable_hot_reload { - return Ok(()) + return Ok(()); } // Create a channel to receive the events. @@ -44,8 +44,8 @@ pub fn hot_reload_files() -> anyhow::Result<()> { let key = route.key(); let file_path = &handler.response.path; - if file_path.to_owned() == PathBuf::new() { - continue + if *file_path == PathBuf::new() { + continue; } let abs_file_path = fs::canonicalize(file_path)?; @@ -62,9 +62,9 @@ pub fn hot_reload_files() -> anyhow::Result<()> { match rx.recv() { Ok(event) => { match event { - DebouncedEvent::Write(file_path) | - DebouncedEvent::Create(file_path) | - DebouncedEvent::Remove(file_path) => { + DebouncedEvent::Write(file_path) + | DebouncedEvent::Create(file_path) + | DebouncedEvent::Remove(file_path) => { if file_path == abs_config_path { // read the configuration file let config = BinserveConfig::read()?; @@ -73,30 +73,27 @@ pub fn hot_reload_files() -> anyhow::Result<()> { let handlebars_handle = templates::render_templates(&config)?; // prepare routes table - RouteHandle::add_routes(&config.routes, &handlebars_handle)?; + RouteHandle::add_routes(&config.routes, &handlebars_handle)?; } - match file_mapping.get(&file_path) { - Some(route_key) => { - // read the configuration file - let config = BinserveConfig::read()?; - - // prepare template partials - let handlebars_handle = templates::render_templates(&config)?; - - // reload the file state and update the global program state - RouteHandle::associate_files_to_routes( - &route_key.to_string(), - &file_path, - &handlebars_handle - )?; - }, - None => () + if let Some(route_key) = file_mapping.get(&file_path) { + // read the configuration file + let config = BinserveConfig::read()?; + + // prepare template partials + let handlebars_handle = templates::render_templates(&config)?; + + // reload the file state and update the global program state + RouteHandle::associate_files_to_routes( + &route_key.to_string(), + &file_path, + &handlebars_handle, + )?; } } - _ => () + _ => (), } - }, + } Err(e) => { println!("[!] filesystem watch error (binserve hot reload): {:?}", e) } From 21416e782459ce43b0804b1cbaef81b4b2471783 Mon Sep 17 00:00:00 2001 From: Logan Kaser Date: Sat, 17 Feb 2024 15:02:45 -0800 Subject: [PATCH 2/3] Update binserve deps --- Cargo.toml | 32 +++++++++--------- src/cli/interface.rs | 9 ++---- src/core/engine.rs | 6 ++-- src/core/mod.rs | 2 +- src/core/server.rs | 2 +- src/core/tls.rs | 23 ++++--------- src/core/watcher.rs | 77 +++++++++++++++++++++++--------------------- 7 files changed, 71 insertions(+), 80 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3965fc1..56144bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,37 +1,37 @@ [package] name = "binserve" -version = "0.2.0" +version = "0.2.1" edition = "2021" [dependencies] actix-files = "0.6.0" -actix-web = { version = "4.0.1", features = ["rustls"] } -actix-web-lab = "0.16.1" -ahash = "0.7.6" +actix-web = { version = "4.5.1", features = ["rustls-0_22"] } +actix-web-lab = "0.20.2" +ahash = "0.8.8" anyhow = "1.0.57" -clap = "3.1.18" +clap = "4.5.1" colored = "2.0.0" -compact_str = "0.4.0" -dashmap = "5.3.4" -env_logger = "0.9.0" -etag = { version = "3.0.0", features = ["std"] } -handlebars = "4.3.1" -jwalk = "0.6.0" -minify-html-onepass = "0.8.0" +compact_str = "0.7.1" +dashmap = "5.5.3" +env_logger = "0.11.2" +etag = { version = "4.0.0", features = ["std"] } +handlebars = "5.1.0" +jwalk = "0.8.1" +minify-html-onepass = "0.15.0" new_mime_guess = "4.0.1" -notify = "4.0.17" +notify-debouncer-mini = "0.4.1" num_cpus = "1.13.1" once_cell = { version = "1.12.0", features = ["parking_lot"] } parking_lot = "0.12.1" -rustls = "0.20.6" -rustls-pemfile = "1.0.0" +rustls = "0.22.2" +rustls-pemfile = "2.1.0" serde = { version = "1.0.137", features = ["derive"] } serde_json = "1.0.81" [profile.release] opt-level = 3 codegen-units = 1 -panic = 'abort' +panic = "abort" lto = "thin" debug = false incremental = false diff --git a/src/cli/interface.rs b/src/cli/interface.rs index 46adbb8..38a81f5 100644 --- a/src/cli/interface.rs +++ b/src/cli/interface.rs @@ -23,21 +23,18 @@ pub fn args() -> ArgMatches { .long("host") .value_name("HOST IP/DOMAIN:PORT") .help("Host to run binserve on.") - .required(false) - .takes_value(true)) + .required(false)) .arg(Arg::new("tls_key") .short('k') .long("key") .value_name("TLS KEY") .help("TLS key file.") - .required(false) - .takes_value(true)) + .required(false)) .arg(Arg::new("tls_cert") .short('c') .long("cert") .value_name("TLS CERT") .help("TLS cert file.") - .required(false) - .takes_value(true)) + .required(false)) .get_matches() } diff --git a/src/core/engine.rs b/src/core/engine.rs index dc99951..ecbd462 100644 --- a/src/core/engine.rs +++ b/src/core/engine.rs @@ -18,13 +18,13 @@ pub fn init() -> anyhow::Result<()> { // override with cli configurations if any let cli_args = interface::args(); - if let Some(host) = cli_args.value_of("host") { + if let Some(host) = cli_args.get_one::("host") { config.server.host = host.into(); } - if let Some(tls_key) = cli_args.value_of("tls_key") { + if let Some(tls_key) = cli_args.get_one::("tls_key") { config.server.tls.key = tls_key.into(); } - if let Some(tls_cert) = cli_args.value_of("tls_cert") { + if let Some(tls_cert) = cli_args.get_one::("tls_cert") { config.server.tls.key = tls_cert.into(); } diff --git a/src/core/mod.rs b/src/core/mod.rs index eb4fedc..f2b0f36 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -7,4 +7,4 @@ pub(super) mod templates; pub(super) mod tls; pub(super) mod watcher; -pub static VERSION: &str = "0.2.0"; +pub static VERSION: &str = "0.2.1"; diff --git a/src/core/server.rs b/src/core/server.rs index 234f15f..3859d65 100644 --- a/src/core/server.rs +++ b/src/core/server.rs @@ -275,7 +275,7 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { let tls_config = tls::load_rustls_config().unwrap(); // bind the TLS host and the rustls configuration - http_server = http_server.bind_rustls(tls_host, tls_config)?; + http_server = http_server.bind_rustls_0_22(tls_host, tls_config)?; } http_server.run().await diff --git a/src/core/tls.rs b/src/core/tls.rs index 1b5697c..b8aa6ed 100644 --- a/src/core/tls.rs +++ b/src/core/tls.rs @@ -1,4 +1,4 @@ -use rustls::{Certificate, PrivateKey, ServerConfig}; +use rustls::ServerConfig; use rustls_pemfile::{certs, pkcs8_private_keys}; use std::fs::File; @@ -15,9 +15,7 @@ pub fn load_rustls_config() -> Result { let config_state = &*CONFIG_STATE.lock(); // init server config builder with safe defaults - let config = ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth(); + let config = ServerConfig::builder().with_no_client_auth(); let cert_file_path = &config_state.server.tls.cert; let cert_key_path = &config_state.server.tls.key; @@ -34,23 +32,14 @@ pub fn load_rustls_config() -> Result { })?); // convert files to key/cert objects - let cert_chain = certs(cert_file) - .unwrap() - .into_iter() - .map(Certificate) - .collect(); - - let mut keys: Vec = pkcs8_private_keys(key_file) - .unwrap() - .into_iter() - .map(PrivateKey) - .collect(); + let cert_chain = certs(cert_file).collect::, _>>()?; + let key = pkcs8_private_keys(key_file).find_map(Result::ok); // exit if no keys could be parsed - if keys.is_empty() { + if key.is_none() { push_message(Type::Error, "Could not locate PKCS 8 private keys."); std::process::exit(1); } - Ok(config.with_single_cert(cert_chain, keys.remove(0))?) + Ok(config.with_single_cert(cert_chain, key.unwrap().into())?) } diff --git a/src/core/watcher.rs b/src/core/watcher.rs index daa26b6..1f9e333 100644 --- a/src/core/watcher.rs +++ b/src/core/watcher.rs @@ -1,10 +1,10 @@ -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; +use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode}; use compact_str::CompactString; use std::collections::HashMap; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::mpsc::channel; use std::time::Duration; @@ -26,14 +26,16 @@ pub fn hot_reload_files() -> anyhow::Result<()> { // Create a watcher object, delivering debounced events. // The notification back-end is selected based on the platform. - let mut watcher = watcher(tx, Duration::from_secs(1))?; + let mut debouncer = new_debouncer(Duration::from_secs(1), tx)?; let mut file_mapping: HashMap = HashMap::with_capacity(ROUTEMAP.len()); // add the binserve config file to the hot reloader let config_file_path = PathBuf::from(CONFIG_FILE); let abs_config_path = fs::canonicalize(config_file_path)?; - watcher.watch(CONFIG_FILE, RecursiveMode::Recursive)?; + debouncer + .watcher() + .watch(Path::new(CONFIG_FILE), RecursiveMode::Recursive)?; // Add a path to be watched. All files and directories at that path and // below will be monitored for changes. @@ -51,7 +53,9 @@ pub fn hot_reload_files() -> anyhow::Result<()> { let abs_file_path = fs::canonicalize(file_path)?; // add to the system filesystem events watch list - watcher.watch(file_path, RecursiveMode::Recursive)?; + debouncer + .watcher() + .watch(file_path, RecursiveMode::Recursive)?; // map them to the corresponding keys in the routemap file_mapping.insert(abs_file_path, key.to_owned()); @@ -60,41 +64,42 @@ pub fn hot_reload_files() -> anyhow::Result<()> { loop { match rx.recv() { - Ok(event) => { - match event { - DebouncedEvent::Write(file_path) - | DebouncedEvent::Create(file_path) - | DebouncedEvent::Remove(file_path) => { - if file_path == abs_config_path { - // read the configuration file - let config = BinserveConfig::read()?; - - // prepare template partials - let handlebars_handle = templates::render_templates(&config)?; - - // prepare routes table - RouteHandle::add_routes(&config.routes, &handlebars_handle)?; - } - - if let Some(route_key) = file_mapping.get(&file_path) { - // read the configuration file - let config = BinserveConfig::read()?; - - // prepare template partials - let handlebars_handle = templates::render_templates(&config)?; - - // reload the file state and update the global program state - RouteHandle::associate_files_to_routes( - &route_key.to_string(), - &file_path, - &handlebars_handle, - )?; - } + Ok(Ok(events)) => { + for event in events { + if event.path == abs_config_path { + // read the configuration file + let config = BinserveConfig::read()?; + + // prepare template partials + let handlebars_handle = templates::render_templates(&config)?; + + // prepare routes table + RouteHandle::add_routes(&config.routes, &handlebars_handle)?; + } + + if let Some(route_key) = file_mapping.get(&event.path) { + // read the configuration file + let config = BinserveConfig::read()?; + + // prepare template partials + let handlebars_handle = templates::render_templates(&config)?; + + // reload the file state and update the global program state + RouteHandle::associate_files_to_routes( + &route_key.to_string(), + &event.path, + &handlebars_handle, + )?; } - _ => (), } } Err(e) => { + println!( + "[!] filesystem watch channel error (binserve hot reload): {:?}", + e + ) + } + Ok(Err(e)) => { println!("[!] filesystem watch error (binserve hot reload): {:?}", e) } } From aec04675e4b019ac4e9b19b71bcf05980f00b8c1 Mon Sep 17 00:00:00 2001 From: Logan Kaser Date: Sat, 17 Feb 2024 15:15:38 -0800 Subject: [PATCH 3/3] Use CARGO_PKG_VERSION --- src/cli/interface.rs | 6 ++---- src/core/mod.rs | 2 -- src/core/server.rs | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/cli/interface.rs b/src/cli/interface.rs index 38a81f5..154fa65 100644 --- a/src/cli/interface.rs +++ b/src/cli/interface.rs @@ -1,16 +1,14 @@ use clap::{Arg, ArgMatches, Command}; -use crate::core::VERSION; - /// Prints an ASCII art banner to look cool! pub fn banner() { - eprintln!("{} {}\n", include_str!("banner"), VERSION) + eprintln!("{} {}\n", include_str!("banner"), env!("CARGO_PKG_VERSION")) } /// Command-line arguments pub fn args() -> ArgMatches { Command::new("binserve") - .version(VERSION) + .version(env!("CARGO_PKG_VERSION")) .author("Mufeed VH ") .about("A fast static web server with Automatic HTTPs, routing, templating, and security in a single binary you can setup with zero code.") .arg(Arg::new("command") diff --git a/src/core/mod.rs b/src/core/mod.rs index f2b0f36..6f7c6fd 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,5 +6,3 @@ pub(super) mod server; pub(super) mod templates; pub(super) mod tls; pub(super) mod watcher; - -pub static VERSION: &str = "0.2.1"; diff --git a/src/core/server.rs b/src/core/server.rs index 3859d65..081b291 100644 --- a/src/core/server.rs +++ b/src/core/server.rs @@ -18,7 +18,7 @@ use std::path::{Path, PathBuf}; use super::{ config::{BinserveConfig, CONFIG_STATE}, routes::{Type, ROUTEMAP}, - tls, VERSION, + tls, }; use crate::cli::messages::{push_message, Type as MsgType}; @@ -158,8 +158,8 @@ pub async fn run_server(config_state: BinserveConfig) -> std::io::Result<()> { let mut headers_middleware = middleware::DefaultHeaders::new(); // binserve server header - headers_middleware = - headers_middleware.add((SERVER, format!("binserve/{}", VERSION))); + headers_middleware = headers_middleware + .add((SERVER, format!("binserve/{}", env!("CARGO_PKG_VERSION")))); // Add the `Cache-Control` header if enabled in config. //