♻️ refactor: clean up ptth_server

- Found I was passing the file server root twice
- Removed handlebars from the public API. The templates are fixed
when ptth_server ships, so I don't think users of the lib should
care what handlebars is.
- Making other stuff private where possible
main
_ 2021-04-17 18:59:59 -05:00
parent 86af3194e5
commit ae33337156
7 changed files with 89 additions and 54 deletions

View File

@ -51,14 +51,8 @@ async fn handle_all (req: Request <Body>, state: Arc <State>)
let ptth_req = RequestParts::from_hyper (parts.method, path_and_query, parts.headers)?; 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 ( let ptth_resp = file_server::serve_all (
&state, &state,
file_server_root,
ptth_req.method, ptth_req.method,
&ptth_req.uri, &ptth_req.uri,
&ptth_req.headers &ptth_req.headers
@ -94,12 +88,6 @@ async fn main () -> anyhow::Result <()> {
let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); 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_interval = Arc::new (ArcSwap::default ()); let metrics_interval = Arc::new (ArcSwap::default ());
let interval_writer = Arc::clone (&metrics_interval); let interval_writer = Arc::clone (&metrics_interval);
@ -107,15 +95,13 @@ async fn main () -> anyhow::Result <()> {
file_server::metrics::Interval::monitor (interval_writer).await; file_server::metrics::Interval::monitor (interval_writer).await;
}); });
let state = Arc::new (State { let state = Arc::new (State::new (
config: file_server::Config { config_file.file_server_root,
file_server_root: config_file.file_server_root, &PathBuf::new (),
}, config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ()),
handlebars,
metrics_startup,
metrics_interval, metrics_interval,
hidden_path: Some (path), Some (path),
}); )?);
let make_svc = make_service_fn (|_conn| { let make_svc = make_service_fn (|_conn| {
let state = state.clone (); let state = state.clone ();

View File

@ -1,20 +1,30 @@
use thiserror::Error; use thiserror::Error;
/// Errors thrown when the server loads its TOML config file
#[derive (Debug, Error)] #[derive (Debug, Error)]
pub enum LoadTomlError { pub enum LoadTomlError {
/// The server's config file is readable by other users, meaning
/// that the file server module could accidentally serve it to
/// clients, and expose the server's API key.
#[error ("Config file has bad permissions mode, it should be octal 0600")] #[error ("Config file has bad permissions mode, it should be octal 0600")]
ConfigBadPermissions, ConfigBadPermissions,
/// Wraps `std::io::Error`
#[error ("I/O")] #[error ("I/O")]
Io (#[from] std::io::Error), Io (#[from] std::io::Error),
/// Wraps `std::string::FromUtf8Error`
#[error ("UTF-8")] #[error ("UTF-8")]
Utf8 (#[from] std::string::FromUtf8Error), Utf8 (#[from] std::string::FromUtf8Error),
/// Wraps `toml::de::Error`
#[error ("TOML")] #[error ("TOML")]
Toml (#[from] toml::de::Error), Toml (#[from] toml::de::Error),
} }
/// Errors thrown when the server is starting up or serving requests
#[derive (Debug, Error)] #[derive (Debug, Error)]
pub enum ServerError { pub enum ServerError {
#[error ("Loading TOML")] #[error ("Loading TOML")]
@ -23,9 +33,6 @@ pub enum ServerError {
#[error ("Loading Handlebars template file")] #[error ("Loading Handlebars template file")]
LoadHandlebars (#[from] handlebars::TemplateFileError), LoadHandlebars (#[from] handlebars::TemplateFileError),
#[error ("API key is too weak, server can't use it")]
WeakApiKey,
#[error ("File server error")] #[error ("File server error")]
FileServer (#[from] super::file_server::errors::FileServerError), FileServer (#[from] super::file_server::errors::FileServerError),

View File

@ -2,9 +2,6 @@ use thiserror::Error;
#[derive (Debug, Error)] #[derive (Debug, Error)]
pub enum FileServerError { pub enum FileServerError {
#[error ("Handlebars render error")]
Handlebars (#[from] handlebars::RenderError),
#[error ("I/O error")] #[error ("I/O error")]
Io (#[from] std::io::Error), Io (#[from] std::io::Error),
@ -28,6 +25,9 @@ pub enum FileServerError {
#[error ("Heim process error")] #[error ("Heim process error")]
HeimProcess, HeimProcess,
#[error(transparent)]
Other (#[from] anyhow::Error),
} }
impl From <heim::process::ProcessError> for FileServerError { impl From <heim::process::ProcessError> for FileServerError {

View File

@ -60,7 +60,7 @@ pub async fn serve_root (
metrics_interval: &**state.metrics_interval.load (), metrics_interval: &**state.metrics_interval.load (),
}; };
let s = state.handlebars.render ("file_server_root", &params)?; let s = state.handlebars.render ("file_server_root", &params).map_err (anyhow::Error::from)?;
Ok (serve (s)) Ok (serve (s))
} }
@ -85,7 +85,7 @@ pub async fn serve_dir (
path, path,
entries, entries,
instance_metrics, instance_metrics,
})?; }).map_err (anyhow::Error::from)?;
Ok (serve (s)) Ok (serve (s))
} }

View File

@ -67,6 +67,27 @@ pub struct State {
pub hidden_path: Option <PathBuf>, pub hidden_path: Option <PathBuf>,
} }
impl State {
pub fn new (
file_server_root: Option <PathBuf>,
asset_root: &Path,
name: String,
metrics_interval: Arc <ArcSwap <Option <metrics::Interval>>>,
hidden_path: Option <PathBuf>,
) -> Result <Self, FileServerError>
{
Ok (Self {
config: Config {
file_server_root,
},
handlebars: load_templates (asset_root)?,
metrics_startup: metrics::Startup::new (name),
metrics_interval,
hidden_path,
})
}
}
#[derive (Serialize)] #[derive (Serialize)]
struct DirJson { struct DirJson {
entries: Vec <DirEntryJson>, entries: Vec <DirEntryJson>,
@ -311,14 +332,14 @@ async fn stream_file (
} }
} }
// Pass a request to the internal decision-making logic. /// Top-level request handler for the file server module.
// When it returns, prettify it as HTML or JSON based on what the client ///
// asked for. /// Passes a request to the internal file server logic.
/// Returns an HTTP response as HTML or JSON, depending on the request.
#[instrument (level = "debug", skip (state, headers))] #[instrument (level = "debug", skip (state, headers))]
pub async fn serve_all ( pub async fn serve_all (
state: &State, state: &State,
root: &Path,
method: Method, method: Method,
uri: &str, uri: &str,
headers: &HashMap <String, Vec <u8>> headers: &HashMap <String, Vec <u8>>
@ -342,6 +363,11 @@ pub async fn serve_all (
resp resp
} }
let default_root = PathBuf::from ("./");
let root: &std::path::Path = state.config.file_server_root
.as_ref ()
.unwrap_or (&default_root);
Ok (match internal::serve_all (root, method, uri, headers, state.hidden_path.as_deref ()).await? { Ok (match internal::serve_all (root, method, uri, headers, state.hidden_path.as_deref ()).await? {
Favicon => serve_error (StatusCode::NotFound, "Not found\n"), Favicon => serve_error (StatusCode::NotFound, "Not found\n"),
Forbidden => serve_error (StatusCode::Forbidden, "403 Forbidden\n"), Forbidden => serve_error (StatusCode::Forbidden, "403 Forbidden\n"),
@ -400,10 +426,10 @@ pub async fn serve_all (
}) })
} }
pub fn load_templates ( fn load_templates (
asset_root: &Path asset_root: &Path
) )
-> Result <Handlebars <'static>, handlebars::TemplateFileError> -> Result <Handlebars <'static>, anyhow::Error>
{ {
let mut handlebars = Handlebars::new (); let mut handlebars = Handlebars::new ();
handlebars.set_strict_mode (true); handlebars.set_strict_mode (true);

View File

@ -1,6 +1,6 @@
//! # PTTH Server //! # PTTH Server
//! //!
//! The PTTH server makes an outgoing HTTP connection to a //! The PTTH server makes outgoing HTTP connections to a
//! PTTH relay, and then serves incoming HTTP requests through //! PTTH relay, and then serves incoming HTTP requests through
//! the relay. //! the relay.
@ -35,7 +35,12 @@ use ptth_core::{
}; };
pub mod errors; pub mod errors;
/// In-process file server module with byte range and ETag support
pub mod file_server; pub mod file_server;
/// Load and de-serialize structures from TOML, with a size limit
/// and checking permissions (On Unix)
pub mod load_toml; pub mod load_toml;
use errors::ServerError; use errors::ServerError;
@ -58,14 +63,8 @@ async fn handle_one_req (
debug! ("Handling request {}", req_id); debug! ("Handling request {}", req_id);
let default_root = PathBuf::from ("./");
let file_server_root: &std::path::Path = state.file_server.config.file_server_root
.as_ref ()
.unwrap_or (&default_root);
let response = file_server::serve_all ( let response = file_server::serve_all (
&state.file_server, &state.file_server,
file_server_root,
parts.method, parts.method,
&parts.uri, &parts.uri,
&parts.headers, &parts.headers,
@ -161,16 +160,30 @@ async fn handle_req_resp (
Ok (()) Ok (())
} }
// A complete config file. The bin frontend is allowed to use a different /// Config for ptth_server and the file server module
// type to load an incomplete file, as long as the command line options ///
// complete it. /// This is a complete config.
/// The bin frontend is allowed to load an incomplete config from
/// the TOML file, fill it out with command-line options, and put
/// the completed config in this struct.
#[derive (Clone)] #[derive (Clone)]
pub struct ConfigFile { pub struct ConfigFile {
/// A name that uniquely identifies this server on the relay.
/// May be human-readable.
pub name: String, pub name: String,
/// Secret API key used to authenticate the server with the relay
pub api_key: String, pub api_key: String,
/// URL of the PTTH relay server that ptth_server should connect to
pub relay_url: String, pub relay_url: String,
/// Directory that the file server module will expose to clients
/// over the relay. If None, the current working dir is used.
pub file_server_root: Option <PathBuf>, pub file_server_root: Option <PathBuf>,
/// For debugging.
pub throttle_upload: bool, pub throttle_upload: bool,
} }
@ -192,12 +205,19 @@ impl ConfigFile {
} }
} }
/// Config for ptth_server itself
#[derive (Default)] #[derive (Default)]
pub struct Config { pub struct Config {
/// URL of the PTTH relay server that ptth_server should connect to
pub relay_url: String, pub relay_url: String,
/// For debugging.
pub throttle_upload: bool, pub throttle_upload: bool,
} }
/// Runs a PTTH file server
pub async fn run_server ( pub async fn run_server (
config_file: ConfigFile, config_file: ConfigFile,
shutdown_oneshot: oneshot::Receiver <()>, shutdown_oneshot: oneshot::Receiver <()>,
@ -224,9 +244,7 @@ pub async fn run_server (
.default_headers (headers) .default_headers (headers)
.connect_timeout (Duration::from_secs (30)) .connect_timeout (Duration::from_secs (30))
.build ().map_err (ServerError::CantBuildHttpClient)?; .build ().map_err (ServerError::CantBuildHttpClient)?;
let handlebars = file_server::load_templates (&asset_root)?;
let metrics_startup = file_server::metrics::Startup::new (config_file.name);
let metrics_interval = Arc::new (ArcSwap::default ()); let metrics_interval = Arc::new (ArcSwap::default ());
let interval_writer = Arc::clone (&metrics_interval); let interval_writer = Arc::clone (&metrics_interval);
@ -235,15 +253,13 @@ pub async fn run_server (
}); });
let state = Arc::new (State { let state = Arc::new (State {
file_server: file_server::State { file_server: file_server::State::new (
config: file_server::Config { config_file.file_server_root,
file_server_root: config_file.file_server_root, &asset_root,
}, config_file.name,
handlebars,
metrics_startup,
metrics_interval, metrics_interval,
hidden_path, hidden_path,
}, )?,
config: Config { config: Config {
relay_url: config_file.relay_url, relay_url: config_file.relay_url,
throttle_upload: config_file.throttle_upload, throttle_upload: config_file.throttle_upload,

View File

@ -26,7 +26,7 @@ fn load_inner <
/// For files that contain public-viewable information /// For files that contain public-viewable information
pub fn load_public < fn load_public <
T: DeserializeOwned, T: DeserializeOwned,
P: AsRef <Path> + Debug P: AsRef <Path> + Debug
> ( > (