update: add InstanceMetrics and replace ServerInfo

main
_ 2020-12-18 20:43:34 +00:00
parent 7a47c705d7
commit d03c1a5476
8 changed files with 125 additions and 30 deletions

3
Cargo.lock generated
View File

@ -1218,6 +1218,7 @@ dependencies = [
"serde", "serde",
"structopt", "structopt",
"tokio", "tokio",
"tracing",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -1257,6 +1258,7 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.12.3", "base64 0.12.3",
"blake3", "blake3",
"chrono",
"futures", "futures",
"handlebars", "handlebars",
"http", "http",
@ -1278,6 +1280,7 @@ dependencies = [
"tracing", "tracing",
"tracing-futures", "tracing-futures",
"tracing-subscriber", "tracing-subscriber",
"ulid",
] ]
[[package]] [[package]]

View File

@ -15,6 +15,7 @@ hyper = "0.13.8"
serde = {version = "1.0.117", features = ["derive"]} serde = {version = "1.0.117", features = ["derive"]}
structopt = "0.3.20" structopt = "0.3.20"
tokio = { version = "0.2.22", features = ["full"] } tokio = { version = "0.2.22", features = ["full"] }
tracing = "0.1.21"
tracing-subscriber = "0.2.15" tracing-subscriber = "0.2.15"
ptth_core = { path = "../ptth_core" } ptth_core = { path = "../ptth_core" }

View File

@ -18,13 +18,17 @@ use hyper::{
StatusCode, StatusCode,
}; };
use serde::Deserialize; use serde::Deserialize;
use tracing::debug;
use ptth_core::{ use ptth_core::{
http_serde::RequestParts, http_serde::RequestParts,
prelude::*, prelude::*,
}; };
use ptth_server::{ use ptth_server::{
file_server, file_server::{
self,
metrics::InstanceMetrics,
},
load_toml, load_toml,
}; };
@ -36,7 +40,7 @@ pub struct Config {
struct ServerState <'a> { struct ServerState <'a> {
config: Config, config: Config,
handlebars: handlebars::Handlebars <'a>, handlebars: handlebars::Handlebars <'a>,
server_info: file_server::ServerInfo, instance_metrics: InstanceMetrics,
hidden_path: Option <PathBuf>, hidden_path: Option <PathBuf>,
} }
@ -63,7 +67,7 @@ async fn handle_all (req: Request <Body>, state: Arc <ServerState <'static>>)
let ptth_resp = file_server::serve_all ( let ptth_resp = file_server::serve_all (
&state.handlebars, &state.handlebars,
&state.server_info, &state.instance_metrics,
file_server_root, file_server_root,
ptth_req.method, ptth_req.method,
&ptth_req.uri, &ptth_req.uri,
@ -101,14 +105,18 @@ async fn main () -> Result <(), anyhow::Error> {
let handlebars = file_server::load_templates (&PathBuf::new ())?; let handlebars = file_server::load_templates (&PathBuf::new ())?;
let instance_metrics = InstanceMetrics::new (
config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ())
);
debug! ("{:?}", instance_metrics);
let state = Arc::new (ServerState { let state = Arc::new (ServerState {
config: Config { config: Config {
file_server_root: config_file.file_server_root, file_server_root: config_file.file_server_root,
}, },
handlebars, handlebars,
server_info: crate::file_server::ServerInfo { instance_metrics,
server_name: config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ()),
},
hidden_path: Some (path), hidden_path: Some (path),
}); });

View File

@ -12,6 +12,7 @@ aho-corasick = "0.7.14"
anyhow = "1.0.34" anyhow = "1.0.34"
base64 = "0.12.3" base64 = "0.12.3"
blake3 = "0.3.7" blake3 = "0.3.7"
chrono = {version = "0.4.19", features = ["serde"]}
futures = "0.3.7" futures = "0.3.7"
handlebars = "3.5.1" handlebars = "3.5.1"
http = "0.2.1" http = "0.2.1"
@ -30,6 +31,7 @@ tracing = "0.1.21"
tracing-futures = "0.2.4" tracing-futures = "0.2.4"
tracing-subscriber = "0.2.15" tracing-subscriber = "0.2.15"
toml = "0.5.7" toml = "0.5.7"
ulid = "0.4.1"
always_equal = { path = "../always_equal" } always_equal = { path = "../always_equal" }
ptth_core = { path = "../ptth_core" } ptth_core = { path = "../ptth_core" }

View File

@ -218,6 +218,9 @@ async fn serve_api (
Ok (NotFound) Ok (NotFound)
} }
// Handle the requests internally without knowing anything about PTTH or
// HTML / handlebars
pub async fn serve_all ( pub async fn serve_all (
root: &Path, root: &Path,
method: Method, method: Method,

View File

@ -0,0 +1,76 @@
use chrono::{DateTime, Utc};
use ulid::Ulid;
fn serialize_ulid <S: serde::Serializer> (t: &Ulid, s: S)
-> Result <S::Ok, S::Error>
{
let t = t.to_string ();
s.serialize_str (&t)
}
// Instance metrics are captured when the ptth_server process starts.
// They don't change after that.
#[derive (Debug, serde::Serialize)]
pub struct InstanceMetrics {
// D-Bus machine ID, if we're on Linux
pub machine_id: Option <String>,
// Git version that ptth_server was built from (unimplemented)
pub _git_version: Option <String>,
// User-assigned and human-readable name for this server.
// Must be unique within a relay.
pub server_name: String,
// Random base64 instance ID. ptth_server generates this at process start.
// It's a fallback for detecting outages without relying on any clocks.
#[serde (serialize_with = "serialize_ulid")]
pub instance_id: Ulid,
pub startup_utc: DateTime <Utc>,
}
fn get_machine_id () -> Option <String> {
use std::{
fs::File,
io::Read,
};
let mut buf = vec! [0u8; 1024];
let mut f = File::open ("/etc/machine-id").ok ()?;
let bytes_read = f.read (&mut buf).ok ()?;
buf.truncate (bytes_read);
let s = std::str::from_utf8 (&buf).ok ()?;
let s = s.trim_end ().to_string ();
Some (s)
}
impl InstanceMetrics {
pub fn new (server_name: String) -> Self
{
Self {
machine_id: get_machine_id (),
_git_version: None,
server_name,
instance_id: ulid::Ulid::new (),
startup_utc: Utc::now (),
}
}
}
#[cfg (test)]
mod tests {
use super::*;
#[test]
fn ulid_null () {
let a = InstanceMetrics::new ("bogus".to_string ());
let b = InstanceMetrics::new ("bogus".to_string ());
assert_ne! (a.instance_id, b.instance_id);
}
}

View File

@ -8,7 +8,6 @@ use std::{
cmp::min, cmp::min,
collections::HashMap, collections::HashMap,
convert::{Infallible, TryFrom}, convert::{Infallible, TryFrom},
fmt::Debug,
io::SeekFrom, io::SeekFrom,
path::Path, path::Path,
}; };
@ -38,11 +37,14 @@ use ptth_core::{
}; };
pub mod errors; pub mod errors;
pub mod metrics;
mod internal; mod internal;
mod markdown; mod markdown;
mod range; mod range;
use errors::FileServerError; use errors::FileServerError;
use metrics::InstanceMetrics;
mod emoji { mod emoji {
pub const VIDEO: &str = "\u{1f39e}\u{fe0f}"; pub const VIDEO: &str = "\u{1f39e}\u{fe0f}";
@ -52,11 +54,6 @@ mod emoji {
pub const ERROR: &str = "\u{26a0}\u{fe0f}"; pub const ERROR: &str = "\u{26a0}\u{fe0f}";
} }
#[derive (Debug, Serialize)]
pub struct ServerInfo {
pub server_name: String,
}
#[derive (Serialize)] #[derive (Serialize)]
struct DirEntryJson { struct DirEntryJson {
name: String, name: String,
@ -93,7 +90,7 @@ struct DirEntryHtml {
#[derive (Serialize)] #[derive (Serialize)]
struct DirHtml <'a> { struct DirHtml <'a> {
#[serde (flatten)] #[serde (flatten)]
server_info: &'a ServerInfo, instance_metrics: &'a InstanceMetrics,
path: Cow <'a, str>, path: Cow <'a, str>,
entries: Vec <DirEntryHtml>, entries: Vec <DirEntryHtml>,
@ -191,10 +188,10 @@ async fn read_dir_entry_json (entry: DirEntry) -> Option <DirEntryJson>
async fn serve_root ( async fn serve_root (
handlebars: &Handlebars <'static>, handlebars: &Handlebars <'static>,
server_info: &ServerInfo instance_metrics: &InstanceMetrics
) -> Result <Response, FileServerError> ) -> Result <Response, FileServerError>
{ {
let s = handlebars.render ("file_server_root", &server_info)?; let s = handlebars.render ("file_server_root", &instance_metrics)?;
Ok (serve_html (s)) Ok (serve_html (s))
} }
@ -233,10 +230,10 @@ async fn serve_dir_json (
Ok (response) Ok (response)
} }
#[instrument (level = "debug", skip (handlebars, dir))] #[instrument (level = "debug", skip (handlebars, instance_metrics, dir))]
async fn serve_dir_html ( async fn serve_dir_html (
handlebars: &Handlebars <'static>, handlebars: &Handlebars <'static>,
server_info: &ServerInfo, instance_metrics: &InstanceMetrics,
path: Cow <'_, str>, path: Cow <'_, str>,
mut dir: ReadDir mut dir: ReadDir
) -> Result <Response, FileServerError> ) -> Result <Response, FileServerError>
@ -252,7 +249,7 @@ async fn serve_dir_html (
let s = handlebars.render ("file_server_dir", &DirHtml { let s = handlebars.render ("file_server_dir", &DirHtml {
path, path,
entries, entries,
server_info, instance_metrics,
})?; })?;
Ok (serve_html (s)) Ok (serve_html (s))
@ -355,10 +352,14 @@ async fn serve_file (
Ok (response) Ok (response)
} }
#[instrument (level = "debug", skip (handlebars, headers))] // 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.
#[instrument (level = "debug", skip (handlebars, headers, instance_metrics))]
pub async fn serve_all ( pub async fn serve_all (
handlebars: &Handlebars <'static>, handlebars: &Handlebars <'static>,
server_info: &ServerInfo, instance_metrics: &InstanceMetrics,
root: &Path, root: &Path,
method: Method, method: Method,
uri: &str, uri: &str,
@ -404,14 +405,14 @@ pub async fn serve_all (
}, },
InvalidQuery => serve_error (StatusCode::BadRequest, "Query is invalid for this object\n"), InvalidQuery => serve_error (StatusCode::BadRequest, "Query is invalid for this object\n"),
Root => serve_root (handlebars, server_info).await?, Root => serve_root (handlebars, instance_metrics).await?,
ServeDir (internal::ServeDirParams { ServeDir (internal::ServeDirParams {
path, path,
dir, dir,
format format
}) => match format { }) => match format {
OutputFormat::Json => serve_dir_json (dir.into_inner ()).await?, OutputFormat::Json => serve_dir_json (dir.into_inner ()).await?,
OutputFormat::Html => serve_dir_html (handlebars, server_info, path.to_string_lossy (), dir.into_inner ()).await?, OutputFormat::Html => serve_dir_html (handlebars, instance_metrics, path.to_string_lossy (), dir.into_inner ()).await?,
}, },
ServeFile (internal::ServeFileParams { ServeFile (internal::ServeFileParams {
file, file,

View File

@ -51,11 +51,14 @@ pub fn password_is_bad (mut password: String) -> bool {
struct ServerState { struct ServerState {
config: Config, config: Config,
handlebars: Handlebars <'static>, handlebars: Handlebars <'static>,
server_info: file_server::ServerInfo, instance_metrics: file_server::metrics::InstanceMetrics,
client: Client, client: Client,
hidden_path: Option <PathBuf>, hidden_path: Option <PathBuf>,
} }
// Unwrap a request from PTTH format and pass it into file_server.
// When file_server responds, wrap it back up and stream it to the relay.
async fn handle_one_req ( async fn handle_one_req (
state: &Arc <ServerState>, state: &Arc <ServerState>,
wrapped_req: http_serde::WrappedRequest wrapped_req: http_serde::WrappedRequest
@ -72,7 +75,7 @@ async fn handle_one_req (
let response = file_server::serve_all ( let response = file_server::serve_all (
&state.handlebars, &state.handlebars,
&state.server_info, &state.instance_metrics,
file_server_root, file_server_root,
parts.method, parts.method,
&parts.uri, &parts.uri,
@ -186,10 +189,6 @@ pub async fn run_server (
return Err (ServerError::WeakApiKey); return Err (ServerError::WeakApiKey);
} }
let server_info = file_server::ServerInfo {
server_name: config_file.name.clone (),
};
info! ("Server name is {}", config_file.name); info! ("Server name is {}", config_file.name);
info! ("Tripcode is {}", config_file.tripcode ()); info! ("Tripcode is {}", config_file.tripcode ());
@ -202,13 +201,15 @@ pub async fn run_server (
.build ().map_err (ServerError::CantBuildHttpClient)?; .build ().map_err (ServerError::CantBuildHttpClient)?;
let handlebars = file_server::load_templates (&asset_root)?; let handlebars = file_server::load_templates (&asset_root)?;
let instance_metrics = file_server::metrics::InstanceMetrics::new (config_file.name);
let state = Arc::new (ServerState { let state = Arc::new (ServerState {
config: Config { config: Config {
relay_url: config_file.relay_url, relay_url: config_file.relay_url,
file_server_root: config_file.file_server_root, file_server_root: config_file.file_server_root,
}, },
handlebars, handlebars,
server_info, instance_metrics,
client, client,
hidden_path, hidden_path,
}); });
@ -233,7 +234,7 @@ pub async fn run_server (
debug! ("http_listen"); debug! ("http_listen");
let req_req = state.client.get (&format! ("{}/http_listen/{}", state.config.relay_url, config_file.name)).send (); let req_req = state.client.get (&format! ("{}/http_listen/{}", state.config.relay_url, state.instance_metrics.server_name)).send ();
let err_backoff_delay = std::cmp::min (30_000, backoff_delay * 2 + 500); let err_backoff_delay = std::cmp::min (30_000, backoff_delay * 2 + 500);