147 lines
4.9 KiB
Rust
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");
|
|
}
|
|
}
|