static-site-server-rs/src/main.rs

147 lines
4.9 KiB
Rust

#![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");
}
}