mod cache;
mod config;
mod handler;
pub mod hteapot;
mod logger;
mod shutdown;
mod utils;
use std::fs;
use std::io;
use std::sync::Mutex;
use cache::Cache;
use hteapot::{Hteapot, HttpRequest, HttpResponse, HttpStatus};
use logger::{LogLevel, Logger};
use std::time::Instant;
use crate::handler::HandlerEngine;
use crate::utils::Context;
fn main() {
let args = std::env::args().collect::<Vec<String>>();
if args.len() == 1 {
println!("Hteapot {}", hteapot::VERSION);
println!("usage: {} <config file>", args[0]);
return;
}
let mut config = match args[1].as_str() {
"--help" | "-h" => {
println!("Hteapot {}", hteapot::VERSION);
println!("usage: {} <config file>", args[0]);
return;
}
"--version" | "-v" => {
println!("Hteapot {}", hteapot::VERSION);
return;
}
"--serve" | "-s" => {
let path = args.get(2).unwrap().clone();
config::Config::new_serve(&path)
}
"--proxy" => {
let c = config::Config::new_proxy();
c
}
_ => config::Config::load_config(&args[1]),
};
if args.contains(&"-p".to_string()) {
let i = args.iter().position(|e| *e == "-p".to_string()).unwrap();
let port = args[i + 1].clone();
let port = port.parse::<u16>();
if port.is_err() {
println!("Invalid port provided");
return;
}
let port = port.unwrap();
config.port = port;
}
let proxy_only = config.proxy_rules.get("/").is_some();
let min_log = if cfg!(debug_assertions) {
LogLevel::DEBUG
} else {
LogLevel::INFO
};
let logger = match config.log_file.clone() {
Some(file_name) => {
let file = fs::File::create(file_name.clone()); match file {
Ok(file) => Logger::new(file, min_log, "main"), Err(e) => {
println!("Failed to create log file: {:?}. Using stdout instead.", e);
Logger::new(io::stdout(), min_log, "main") }
}
}
None => Logger::new(io::stdout(), min_log, "main"), };
let cache: Mutex<Cache<HttpRequest, HttpResponse>> =
Mutex::new(Cache::new(config.cache_ttl as u64));
let mut server = Hteapot::new_threaded(config.host.as_str(), config.port, config.threads);
shutdown::setup_graceful_shutdown(&mut server, logger.clone());
logger.info(format!(
"Server started at http://{}:{}",
config.host, config.port
));
if config.cache {
logger.info("Cache Enabled".to_string());
}
if proxy_only {
logger
.warn("WARNING: All requests are proxied to /. Local paths won't be used.".to_string());
}
let cache_logger = logger.with_component("cache");
let http_logger = logger.with_component("http");
let handlers = HandlerEngine::new();
server.listen(move |req: HttpRequest| {
let start_time = Instant::now(); let req_method = req.method.to_str();
http_logger.info(format!("Request {} {}", req_method, req.path));
if config.cache {
let cache_start = Instant::now(); let mut cache_lock = cache.lock().expect("Error locking cache");
if let Some(response) = cache_lock.get(&req) {
cache_logger.debug(format!("cache hit for {}", &req.path));
let elapsed = start_time.elapsed();
http_logger.debug(format!(
"Request processed in {:.6}ms",
elapsed.as_secs_f64() * 1000.0 ));
return Box::new(response);
} else {
cache_logger.debug(format!("cache miss for {}", &req.path));
}
let cache_elapsed = cache_start.elapsed();
cache_logger.debug(format!(
"Cache operation completed in {:.6}ยตs",
cache_elapsed.as_micros()
));
}
let mut ctx = Context {
request: &req,
log: &logger,
config: &config,
cache: if config.cache {
Some(&mut cache.lock().unwrap())
} else {
None
},
};
let response = handlers.get_handler(&ctx);
if response.is_none() {
return HttpResponse::new(HttpStatus::InternalServerError, "content", None);
}
let response = response.unwrap().run(&mut ctx);
let elapsed = start_time.elapsed();
http_logger.debug(format!(
"Request processed in {:.6}ms",
elapsed.as_secs_f64() * 1000.0 ));
response
});
}