use hyper::server::Server as HyperServer;
use hyper::service::{make_service_fn, service_fn};
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use structopt::StructOpt;
use crate::{
config::{Config, CONFIG},
error_page,
};
use crate::{error, helpers, logger, Result};
use crate::{handler, static_files::ArcPath};
pub struct Server {
threads: usize,
}
impl Server {
pub fn new() -> Self {
CONFIG.set(Config::from_args()).unwrap();
let opts = Config::global();
let cpus = num_cpus::get();
let threads = match opts.threads_multiplier {
0 | 1 => cpus,
n => cpus * n,
};
Self { threads }
}
pub fn run(self) -> Result {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("static-web-server")
.worker_threads(self.threads)
.build()?
.block_on(async {
let r = self.start_server().await;
if r.is_err() {
panic!("Server error during start up: {:?}", r.unwrap_err())
}
});
Ok(())
}
async fn start_server(self) -> Result {
let opts = Config::global();
logger::init(&opts.log_level)?;
tracing::info!("runtime worker threads {}", self.threads);
tracing::info!("runtime max blocking threads {}", self.threads);
let ip = opts.host.parse::<IpAddr>()?;
let addr = SocketAddr::from((ip, opts.port));
let root_dir = helpers::get_valid_dirpath(&opts.root)?;
error_page::PAGE_404
.set(helpers::read_file_content(opts.page404.as_ref()))
.expect("page 404 is not initialized");
error_page::PAGE_50X
.set(helpers::read_file_content(opts.page50x.as_ref()))
.expect("page 50x is not initialized");
tokio::task::spawn(async move {
let span = tracing::info_span!("Server::run", ?addr, threads = ?self.threads);
tracing::info!(parent: &span, "listening on http://{}", addr);
let root_dir = ArcPath(Arc::new(root_dir));
let create_service = make_service_fn(move |_| {
let root_dir = root_dir.clone();
async move {
Ok::<_, error::Error>(service_fn(move |req| {
let root_dir = root_dir.clone();
async move { handler::handle_request(root_dir.as_ref(), &req).await }
}))
}
});
HyperServer::bind(&addr).serve(create_service).await
});
handle_signals();
Ok(())
}
}
impl Default for Server {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(windows))]
fn handle_signals() {
use crate::signals;
signals::wait(|sig: signals::Signal| {
let code = signals::as_int(sig);
tracing::warn!("Signal {} caught. Server execution exited.", code);
std::process::exit(code)
});
}
#[cfg(windows)]
fn handle_signals() {
}