diff --git a/crates/ptth_file_server_bin/src/main.rs b/crates/ptth_file_server_bin/src/main.rs
index d83b122..874dd88 100644
--- a/crates/ptth_file_server_bin/src/main.rs
+++ b/crates/ptth_file_server_bin/src/main.rs
@@ -51,14 +51,8 @@ async fn handle_all (req: Request
, state: Arc )
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
@@ -94,12 +88,6 @@ async fn main () -> anyhow::Result <()> {
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 interval_writer = Arc::clone (&metrics_interval);
@@ -107,15 +95,13 @@ async fn main () -> anyhow::Result <()> {
file_server::metrics::Interval::monitor (interval_writer).await;
});
- let state = Arc::new (State {
- config: file_server::Config {
- file_server_root: config_file.file_server_root,
- },
- handlebars,
- metrics_startup,
+ let state = Arc::new (State::new (
+ config_file.file_server_root,
+ &PathBuf::new (),
+ config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ()),
metrics_interval,
- hidden_path: Some (path),
- });
+ Some (path),
+ )?);
let make_svc = make_service_fn (|_conn| {
let state = state.clone ();
diff --git a/crates/ptth_server/src/errors.rs b/crates/ptth_server/src/errors.rs
index 4ebdbe1..cfdb276 100644
--- a/crates/ptth_server/src/errors.rs
+++ b/crates/ptth_server/src/errors.rs
@@ -1,20 +1,30 @@
use thiserror::Error;
+/// Errors thrown when the server loads its TOML config file
+
#[derive (Debug, Error)]
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")]
ConfigBadPermissions,
+ /// Wraps `std::io::Error`
#[error ("I/O")]
Io (#[from] std::io::Error),
+ /// Wraps `std::string::FromUtf8Error`
#[error ("UTF-8")]
Utf8 (#[from] std::string::FromUtf8Error),
+ /// Wraps `toml::de::Error`
#[error ("TOML")]
Toml (#[from] toml::de::Error),
}
+/// Errors thrown when the server is starting up or serving requests
+
#[derive (Debug, Error)]
pub enum ServerError {
#[error ("Loading TOML")]
@@ -23,9 +33,6 @@ pub enum ServerError {
#[error ("Loading Handlebars template file")]
LoadHandlebars (#[from] handlebars::TemplateFileError),
- #[error ("API key is too weak, server can't use it")]
- WeakApiKey,
-
#[error ("File server error")]
FileServer (#[from] super::file_server::errors::FileServerError),
diff --git a/crates/ptth_server/src/file_server/errors.rs b/crates/ptth_server/src/file_server/errors.rs
index 7be513c..cbae2d8 100644
--- a/crates/ptth_server/src/file_server/errors.rs
+++ b/crates/ptth_server/src/file_server/errors.rs
@@ -2,9 +2,6 @@ use thiserror::Error;
#[derive (Debug, Error)]
pub enum FileServerError {
- #[error ("Handlebars render error")]
- Handlebars (#[from] handlebars::RenderError),
-
#[error ("I/O error")]
Io (#[from] std::io::Error),
@@ -28,6 +25,9 @@ pub enum FileServerError {
#[error ("Heim process error")]
HeimProcess,
+
+ #[error(transparent)]
+ Other (#[from] anyhow::Error),
}
impl From for FileServerError {
diff --git a/crates/ptth_server/src/file_server/html.rs b/crates/ptth_server/src/file_server/html.rs
index d729074..d221058 100644
--- a/crates/ptth_server/src/file_server/html.rs
+++ b/crates/ptth_server/src/file_server/html.rs
@@ -60,7 +60,7 @@ pub async fn serve_root (
metrics_interval: &**state.metrics_interval.load (),
};
- let s = state.handlebars.render ("file_server_root", ¶ms)?;
+ let s = state.handlebars.render ("file_server_root", ¶ms).map_err (anyhow::Error::from)?;
Ok (serve (s))
}
@@ -85,7 +85,7 @@ pub async fn serve_dir (
path,
entries,
instance_metrics,
- })?;
+ }).map_err (anyhow::Error::from)?;
Ok (serve (s))
}
diff --git a/crates/ptth_server/src/file_server/mod.rs b/crates/ptth_server/src/file_server/mod.rs
index be21882..dec094a 100644
--- a/crates/ptth_server/src/file_server/mod.rs
+++ b/crates/ptth_server/src/file_server/mod.rs
@@ -67,6 +67,27 @@ pub struct State {
pub hidden_path: Option ,
}
+impl State {
+ pub fn new (
+ file_server_root: Option ,
+ asset_root: &Path,
+ name: String,
+ metrics_interval: Arc >>,
+ hidden_path: Option ,
+ ) -> Result
+ {
+ Ok (Self {
+ config: Config {
+ file_server_root,
+ },
+ handlebars: load_templates (asset_root)?,
+ metrics_startup: metrics::Startup::new (name),
+ metrics_interval,
+ hidden_path,
+ })
+ }
+}
+
#[derive (Serialize)]
struct DirJson {
entries: Vec ,
@@ -311,14 +332,14 @@ async fn stream_file (
}
}
-// Pass a request to the internal decision-making logic.
-// When it returns, prettify it as HTML or JSON based on what the client
-// asked for.
+/// Top-level request handler for the file server module.
+///
+/// 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))]
pub async fn serve_all (
state: &State,
- root: &Path,
method: Method,
uri: &str,
headers: &HashMap >
@@ -342,6 +363,11 @@ pub async fn serve_all (
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? {
Favicon => serve_error (StatusCode::NotFound, "Not found\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
)
--> Result , handlebars::TemplateFileError>
+-> Result , anyhow::Error>
{
let mut handlebars = Handlebars::new ();
handlebars.set_strict_mode (true);
diff --git a/crates/ptth_server/src/lib.rs b/crates/ptth_server/src/lib.rs
index 89a49b0..9208a44 100644
--- a/crates/ptth_server/src/lib.rs
+++ b/crates/ptth_server/src/lib.rs
@@ -1,6 +1,6 @@
//! # 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
//! the relay.
@@ -35,7 +35,12 @@ use ptth_core::{
};
pub mod errors;
+
+/// In-process file server module with byte range and ETag support
pub mod file_server;
+
+/// Load and de-serialize structures from TOML, with a size limit
+/// and checking permissions (On Unix)
pub mod load_toml;
use errors::ServerError;
@@ -58,14 +63,8 @@ async fn handle_one_req (
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 (
&state.file_server,
- file_server_root,
parts.method,
&parts.uri,
&parts.headers,
@@ -161,16 +160,30 @@ async fn handle_req_resp (
Ok (())
}
-// A complete config file. The bin frontend is allowed to use a different
-// type to load an incomplete file, as long as the command line options
-// complete it.
+/// Config for ptth_server and the file server module
+///
+/// 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)]
pub struct ConfigFile {
+ /// A name that uniquely identifies this server on the relay.
+ /// May be human-readable.
pub name: String,
+
+ /// Secret API key used to authenticate the server with the relay
pub api_key: String,
+
+ /// URL of the PTTH relay server that ptth_server should connect to
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 ,
+
+ /// For debugging.
pub throttle_upload: bool,
}
@@ -192,12 +205,19 @@ impl ConfigFile {
}
}
+/// Config for ptth_server itself
+
#[derive (Default)]
pub struct Config {
+ /// URL of the PTTH relay server that ptth_server should connect to
pub relay_url: String,
+
+ /// For debugging.
pub throttle_upload: bool,
}
+/// Runs a PTTH file server
+
pub async fn run_server (
config_file: ConfigFile,
shutdown_oneshot: oneshot::Receiver <()>,
@@ -224,9 +244,7 @@ pub async fn run_server (
.default_headers (headers)
.connect_timeout (Duration::from_secs (30))
.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 interval_writer = Arc::clone (&metrics_interval);
@@ -235,15 +253,13 @@ pub async fn run_server (
});
let state = Arc::new (State {
- file_server: file_server::State {
- config: file_server::Config {
- file_server_root: config_file.file_server_root,
- },
- handlebars,
- metrics_startup,
+ file_server: file_server::State::new (
+ config_file.file_server_root,
+ &asset_root,
+ config_file.name,
metrics_interval,
hidden_path,
- },
+ )?,
config: Config {
relay_url: config_file.relay_url,
throttle_upload: config_file.throttle_upload,
diff --git a/crates/ptth_server/src/load_toml.rs b/crates/ptth_server/src/load_toml.rs
index 6851467..2637ca9 100644
--- a/crates/ptth_server/src/load_toml.rs
+++ b/crates/ptth_server/src/load_toml.rs
@@ -26,7 +26,7 @@ fn load_inner <
/// For files that contain public-viewable information
-pub fn load_public <
+fn load_public <
T: DeserializeOwned,
P: AsRef + Debug
> (