#![warn (clippy::pedantic)] use std::{ net::SocketAddr, path::PathBuf, sync::Arc, }; use arc_swap::ArcSwap; use hyper::{ Body, Request, Response, Server, service::{ make_service_fn, service_fn, }, StatusCode, }; use serde::Deserialize; use tracing::debug; use ptth_core::{ http_serde::RequestParts, prelude::*, }; use ptth_server::{ file_server::{ self, metrics, State, }, load_toml, }; async fn handle_all (req: Request , state: Arc ) -> Result , anyhow::Error> { use std::str::FromStr; use hyper::header::HeaderName; debug! ("req.uri () = {:?}", req.uri ()); let path_and_query = req.uri ().path_and_query ().map_or_else (|| req.uri ().path (), http::uri::PathAndQuery::as_str); let path_and_query = path_and_query.into (); let (parts, _) = req.into_parts (); let ptth_req = RequestParts::from_hyper (parts.method, path_and_query, parts.headers)?; let default_root = PathBuf::from ("./"); let file_server_root: &std::path::Path = state.config.file_server_root .as_ref () .unwrap_or (&default_root); let ptth_resp = file_server::serve_all ( &state, file_server_root, ptth_req.method, &ptth_req.uri, &ptth_req.headers ).await?; let mut resp = Response::builder () .status (StatusCode::from (ptth_resp.parts.status_code)); for (k, v) in ptth_resp.parts.headers { resp = resp.header (HeaderName::from_str (&k)?, v); } let body = ptth_resp.body.map_or_else (Body::empty, Body::wrap_stream); Ok (resp.body (body)?) } #[derive (Deserialize)] pub struct ConfigFile { pub file_server_root: Option , pub name: Option , } #[tokio::main] async fn main () -> Result <(), anyhow::Error> { tracing_subscriber::fmt::init (); let path = PathBuf::from ("./config/ptth_server.toml"); let config_file: ConfigFile = load_toml::load (&path)?; info! ("file_server_root: {:?}", config_file.file_server_root); let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); let handlebars = file_server::load_templates (&PathBuf::new ())?; let metrics_startup = metrics::Startup::new ( config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ()) ); let metrics_gauges = Arc::new (ArcSwap::default ()); let gauge_writer = Arc::clone (&metrics_gauges); tokio::spawn (async move { let mut interval = tokio::time::interval (std::time::Duration::from_secs (2)); loop { interval.tick ().await; let new_gauges = match file_server::metrics::Gauges::new ().await { Err (e) => { error! ("Failed to update gauge metrics: {:?}", e); continue; }, Ok (x) => x, }; let new_gauges = Arc::new (Some (new_gauges)); gauge_writer.store (new_gauges); } }); let state = Arc::new (State { config: file_server::Config { file_server_root: config_file.file_server_root, }, handlebars, metrics_startup, metrics_gauges, hidden_path: Some (path), }); let make_svc = make_service_fn (|_conn| { let state = state.clone (); async { Ok::<_, String> (service_fn (move |req| { let state = state.clone (); handle_all (req, state) })) } }); let (shutdown_rx, forced_shutdown) = ptth_core::graceful_shutdown::init_with_force (); let server = Server::bind (&addr) .serve (make_svc) .with_graceful_shutdown (async move { shutdown_rx.await.ok (); }); forced_shutdown.wrap_server (server).await??; Ok (()) }