#![feature(let_chains)] use clap::Parser; use std::fs; use std::net::TcpListener; use std::path::{Path, PathBuf}; use std::time::Duration; use text_template::TextTemplate; use threadpool::ThreadPool; use utils::content_type_for_exension; mod http_request; mod http_response; mod server_config; mod server_routes_scan; mod static_http_server; mod static_http_server_route; mod subdomain_info; mod text_template; mod text_template_result; mod utils; use server_config::ServerConfig; use static_http_server::StaticHttpServer; #[derive(clap::Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { config_yaml: PathBuf, #[arg(short, long, default_value_t = false)] test: bool, #[arg(short, long, default_value_t = false)] compile_template: bool, #[arg(short, long, default_value_t = false)] inflate_template: bool, } fn main() { let args = Args::parse(); match args.compile_template { false => match args.inflate_template { false => match args.test { false => serve_with_config_path(&args.config_yaml), true => test_config_path(&args.config_yaml), }, true => inflate_template_path(&args.config_yaml, args.test), }, true => compile_template_path(&args.config_yaml, args.test), } } fn read_config(config: &PathBuf) -> ServerConfig { serde_yaml::from_str( std::fs::read_to_string(config) .expect("FATAL: Could not read config file") .as_str(), ) .expect("FATAL: Could not parse config file") } fn test_config_path(config: &PathBuf) { read_config(config); std::env::args().next().and_then(|prog| { config.to_str().and_then(|s| { println!("{prog}: configuration file {s} test is successful"); None::<()> }) }); } fn serve_with_config_path(config: &PathBuf) { serve_with_config(read_config(config)) } fn serve_with_config(config: ServerConfig) { let bind_interface = format!("{}:{}", config.bind, config.port); let listener = TcpListener::bind(&bind_interface) .expect("FATAL: Could not bind TCP port specified by provided configuration"); let pool = ThreadPool::new(config.threads); println!( "Listening on http://{} with {} threads", bind_interface, config.threads, ); let mut static_server = StaticHttpServer::new(config); for stream_result in listener.incoming() { static_server.check_for_changes(); let _ = stream_result.and_then(|stream| { stream.set_read_timeout(Some(Duration::from_secs(2)))?; let cloned = static_server.clone(); pool.execute(move || cloned.handle_stream(stream)); Ok(()) }); } } fn compile_template_path(template_path: &Path, dry_run: bool) { let template_path = template_path .canonicalize() .expect("textual template should exist"); let ext = template_path .extension() .expect("compiled template should have an extension") .to_str() .expect("extension should be in the unicode range"); if !ext.ends_with("tpl") { panic!("extension should end with tpl"); } let ext = &ext[..ext.len() - 3]; let mime = content_type_for_exension(Some(ext)) .unwrap_or_else(|| panic!("template extension {ext:?} should have known mimetype")); let template_source_contents = fs::read_to_string(&template_path).expect("file should exist and contain utf-8 data"); let template_content_data = TextTemplate::generate(Some(&mime), &template_source_contents) .expect("template should have been parsed successfully"); let template_contents = serde_yaml::to_string(&template_content_data) .expect("parsed template should be serializeable"); let destpath = template_path.with_extension(format!("{ext}.tpl")); if dry_run { println!("template generation was successful"); } else { fs::write(destpath, template_contents).expect("could not write destination file"); } } fn inflate_template_path(template_path: &Path, dry_run: bool) { let template_path = template_path .canonicalize() .expect("textual template should exist"); let template_text = fs::read_to_string(&template_path).expect("file exist and be utf-8 encoded"); let template = TextTemplate::try_from(&template_text as &str).expect("parsed sucessfully"); let template_contents = template.reinflate(); let fnm = template_path .file_name() .expect("file has filename") .to_str() .unwrap(); let fnm_len_minus_4 = fnm.len() - 4; let destpath = template_path.with_file_name(format!("{}tpl", fnm[..fnm_len_minus_4].to_owned())); if dry_run { println!("template generation was successful"); } else { fs::write(destpath, template_contents).expect("could not write destination file"); } }