Compare commits
No commits in common. "98b43d1ba2371e720f7dd4b3b43d6fd6d445c032" and "c5aadbe632c6cb34cbe6a9e5f2b9ab66f36ff6b9" have entirely different histories.
98b43d1ba2
...
c5aadbe632
File diff suppressed because it is too large
Load Diff
|
@ -23,7 +23,7 @@ exclude = [
|
|||
[dependencies]
|
||||
|
||||
anyhow = "1.0.38"
|
||||
blake3 = "1.0.0"
|
||||
blake3 = "0.3.7"
|
||||
tokio = { version = "1.4.0", features = ["full"] }
|
||||
tracing-subscriber = "0.2.16"
|
||||
tracing = "0.1.25"
|
||||
|
|
|
@ -10,7 +10,6 @@ description = "Common code for the PTTH relay and server"
|
|||
|
||||
[dependencies]
|
||||
|
||||
anyhow = "1.0.38"
|
||||
base64 = "0.13.0"
|
||||
ctrlc = { version = "3.1.8", features = [ "termination" ] }
|
||||
futures = "0.3.7"
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
pub use std::{
|
||||
ffi::OsString,
|
||||
io::Write,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub use anyhow::{
|
||||
Context,
|
||||
bail,
|
||||
};
|
||||
pub use tracing::{
|
||||
debug, error, info, trace, warn,
|
||||
instrument,
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
#![warn (clippy::pedantic)]
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
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 tokio_stream::wrappers::ReceiverStream;
|
||||
use tracing::debug;
|
||||
|
||||
use ptth_core::{
|
||||
http_serde::RequestParts,
|
||||
prelude::*,
|
||||
};
|
||||
use ptth_server::{
|
||||
file_server::{
|
||||
self,
|
||||
metrics,
|
||||
FileServer,
|
||||
},
|
||||
load_toml,
|
||||
};
|
||||
|
||||
async fn handle_all (req: Request <Body>, state: Arc <FileServer>)
|
||||
-> anyhow::Result <Response <Body>>
|
||||
{
|
||||
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 ptth_resp = state.serve_all (
|
||||
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| {
|
||||
Body::wrap_stream (ReceiverStream::new (body))
|
||||
});
|
||||
|
||||
Ok (resp.body (body)?)
|
||||
}
|
||||
|
||||
#[derive (Deserialize)]
|
||||
struct ConfigFile {
|
||||
file_server_root: Option <PathBuf>,
|
||||
name: Option <String>,
|
||||
}
|
||||
|
||||
pub async fn main (_args: &[OsString]) -> anyhow::Result <()> {
|
||||
let path = PathBuf::from ("./config/ptth_server.toml");
|
||||
|
||||
let file_server_root;
|
||||
let name;
|
||||
|
||||
match load_toml::load::<ConfigFile, _> (&path) {
|
||||
Ok (config_file) => {
|
||||
file_server_root = config_file.file_server_root;
|
||||
name = config_file.name;
|
||||
},
|
||||
_ => {
|
||||
info! ("No ptth_server.toml file, using default configs");
|
||||
file_server_root = None;
|
||||
name = None;
|
||||
},
|
||||
};
|
||||
|
||||
let name = name.unwrap_or_else (|| "PTTH File Server".to_string ());
|
||||
|
||||
info! ("file_server_root: {:?}", file_server_root);
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 4000));
|
||||
info! ("Serving at {:?}", addr);
|
||||
|
||||
let metrics_interval = Arc::new (ArcSwap::default ());
|
||||
|
||||
let interval_writer = Arc::clone (&metrics_interval);
|
||||
tokio::spawn (async move {
|
||||
file_server::metrics::Interval::monitor (interval_writer).await;
|
||||
});
|
||||
|
||||
let state = Arc::new (FileServer::new (
|
||||
file_server_root,
|
||||
&PathBuf::new (),
|
||||
name,
|
||||
metrics_interval,
|
||||
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 (())
|
||||
}
|
||||
|
|
@ -1,12 +1,128 @@
|
|||
#![warn (clippy::pedantic)]
|
||||
|
||||
use std::{
|
||||
iter::FromIterator,
|
||||
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 tokio_stream::wrappers::ReceiverStream;
|
||||
use tracing::debug;
|
||||
|
||||
use ptth_core::{
|
||||
http_serde::RequestParts,
|
||||
prelude::*,
|
||||
};
|
||||
use ptth_server::{
|
||||
file_server::{
|
||||
self,
|
||||
metrics,
|
||||
FileServer,
|
||||
},
|
||||
load_toml,
|
||||
};
|
||||
|
||||
async fn handle_all (req: Request <Body>, state: Arc <FileServer>)
|
||||
-> anyhow::Result <Response <Body>>
|
||||
{
|
||||
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 ptth_resp = state.serve_all (
|
||||
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| {
|
||||
Body::wrap_stream (ReceiverStream::new (body))
|
||||
});
|
||||
|
||||
Ok (resp.body (body)?)
|
||||
}
|
||||
|
||||
#[derive (Deserialize)]
|
||||
pub struct ConfigFile {
|
||||
pub file_server_root: Option <PathBuf>,
|
||||
pub name: Option <String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main () -> anyhow::Result <()> {
|
||||
tracing_subscriber::fmt::init ();
|
||||
|
||||
let args = Vec::from_iter (std::env::args_os ());
|
||||
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);
|
||||
|
||||
ptth_file_server::main (&args).await
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 4000));
|
||||
|
||||
let metrics_interval = Arc::new (ArcSwap::default ());
|
||||
|
||||
let interval_writer = Arc::clone (&metrics_interval);
|
||||
tokio::spawn (async move {
|
||||
file_server::metrics::Interval::monitor (interval_writer).await;
|
||||
});
|
||||
|
||||
let state = Arc::new (FileServer::new (
|
||||
config_file.file_server_root,
|
||||
&PathBuf::new (),
|
||||
config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ()),
|
||||
metrics_interval,
|
||||
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 (())
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
[package]
|
||||
name = "ptth_multi_call_server"
|
||||
version = "0.1.0"
|
||||
authors = ["Trish"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.38"
|
||||
ptth_file_server = { path = "../ptth_file_server_bin" }
|
||||
ptth_server = { path = "../ptth_server" }
|
||||
quic_demo = { path = "../../prototypes/quic_demo" }
|
||||
tokio = { version = "1.8.1", features = ["full"] }
|
||||
tracing-subscriber = "0.2.16"
|
||||
tracing = "0.1.25"
|
|
@ -1,143 +0,0 @@
|
|||
use std::{
|
||||
ffi::OsString,
|
||||
iter::FromIterator,
|
||||
};
|
||||
|
||||
#[derive (Clone, Copy, Debug, PartialEq)]
|
||||
enum Subcommand {
|
||||
PtthServer,
|
||||
PtthFileServer,
|
||||
PtthQuicEndServer,
|
||||
}
|
||||
|
||||
fn parse_subcommand (arg: &str) -> Option <Subcommand>
|
||||
{
|
||||
use Subcommand::*;
|
||||
|
||||
let map = vec! [
|
||||
("ptth_server", PtthServer),
|
||||
("ptth_file_server", PtthFileServer),
|
||||
("ptth_quic_end_server", PtthQuicEndServer),
|
||||
];
|
||||
|
||||
let arg = arg.strip_suffix (".exe").unwrap_or (arg);
|
||||
|
||||
for (suffix, subcommand) in &map {
|
||||
if arg.ends_with (suffix) {
|
||||
return Some (*subcommand);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_args (args: &[OsString]) -> anyhow::Result <(Subcommand, &[OsString])>
|
||||
{
|
||||
let arg_0 = match args.get (0) {
|
||||
Some (x) => x,
|
||||
None => anyhow::bail! ("arg 0 must be the exe name"),
|
||||
};
|
||||
|
||||
let arg_0 = arg_0.to_str ().ok_or_else (|| anyhow::anyhow! ("arg 0 should be valid UTF-8"))?;
|
||||
match parse_subcommand (arg_0) {
|
||||
Some (x) => return Ok ((x, args)),
|
||||
None => (),
|
||||
}
|
||||
|
||||
let arg_1 = match args.get (1) {
|
||||
Some (x) => x,
|
||||
None => anyhow::bail! ("arg 1 must be the subcommand if arg 0 is not"),
|
||||
};
|
||||
|
||||
let arg_1 = arg_1.to_str ().ok_or_else (|| anyhow::anyhow! ("arg 1 subcommand should be valid UTF-8"))?;
|
||||
match parse_subcommand (arg_1) {
|
||||
Some (x) => return Ok ((x, &args [1..])),
|
||||
None => (),
|
||||
}
|
||||
|
||||
anyhow::bail! ("Subcommand must be either arg 0 (exe name) or arg 1")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main () -> anyhow::Result <()> {
|
||||
tracing_subscriber::fmt::init ();
|
||||
|
||||
let args = Vec::from_iter (std::env::args_os ());
|
||||
|
||||
let (subcommand, args) = parse_args (&args)?;
|
||||
match subcommand {
|
||||
Subcommand::PtthServer => ptth_server::executable::main (&args).await,
|
||||
Subcommand::PtthFileServer => ptth_file_server::main (&args).await,
|
||||
Subcommand::PtthQuicEndServer => quic_demo::executable_end_server::main (&args).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg (test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn multi_call () -> anyhow::Result <()> {
|
||||
let negative_cases = vec! [
|
||||
vec! [],
|
||||
vec! ["invalid_exe_name"],
|
||||
vec! ["ptth_multi_call_server"],
|
||||
vec! ["ptth_server.ex"],
|
||||
vec! ["ptth_multi_call_server", "invalid_subcommand"],
|
||||
];
|
||||
|
||||
for input in &negative_cases {
|
||||
let input: Vec <_> = input.iter ().map (OsString::from).collect ();
|
||||
|
||||
let actual = parse_args (&input);
|
||||
assert! (actual.is_err ());
|
||||
}
|
||||
|
||||
let positive_cases = vec! [
|
||||
(vec! ["ptth_server.exe"], (Subcommand::PtthServer, vec! ["ptth_server.exe"])),
|
||||
(vec! ["ptth_server"], (Subcommand::PtthServer, vec! ["ptth_server"])),
|
||||
(vec! ["ptth_server", "--help"], (Subcommand::PtthServer, vec! ["ptth_server", "--help"])),
|
||||
(vec! ["ptth_file_server"], (Subcommand::PtthFileServer, vec! ["ptth_file_server"])),
|
||||
(vec! ["ptth_quic_end_server", "--help"], (Subcommand::PtthQuicEndServer, vec! ["ptth_quic_end_server", "--help"])),
|
||||
(vec! ["ptth_multi_call_server", "ptth_server"], (Subcommand::PtthServer, vec! ["ptth_server"])),
|
||||
(
|
||||
vec! [
|
||||
"ptth_multi_call_server",
|
||||
"ptth_server",
|
||||
"--help"
|
||||
],
|
||||
(
|
||||
Subcommand::PtthServer,
|
||||
vec! [
|
||||
"ptth_server",
|
||||
"--help"
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
vec! [
|
||||
"invalid_exe_name",
|
||||
"ptth_server",
|
||||
"--help"
|
||||
],
|
||||
(
|
||||
Subcommand::PtthServer,
|
||||
vec! [
|
||||
"ptth_server",
|
||||
"--help"
|
||||
]
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
for (input, (expected_subcommand, expected_args)) in &positive_cases {
|
||||
let input: Vec <_> = input.iter ().map (OsString::from).collect ();
|
||||
|
||||
let (actual_subcommand, actual_args) = parse_args (&input)?;
|
||||
assert_eq! (expected_subcommand, &actual_subcommand);
|
||||
assert_eq! (expected_args, actual_args);
|
||||
}
|
||||
|
||||
Ok (())
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ description = "The PTTH relay"
|
|||
|
||||
anyhow = "1.0.38"
|
||||
base64 = "0.13.0"
|
||||
blake3 = "1.0.0"
|
||||
blake3 = "0.3.7"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
clap = "2.33.3"
|
||||
dashmap = "4.0.2"
|
||||
|
|
|
@ -17,7 +17,7 @@ aho-corasick = "0.7.15"
|
|||
anyhow = "1.0.38"
|
||||
arc-swap = "1.2.0"
|
||||
base64 = "0.13.0"
|
||||
blake3 = "1.0.0"
|
||||
blake3 = "0.3.7"
|
||||
chrono = {version = "0.4.19", features = ["serde"]}
|
||||
futures = "0.3.7"
|
||||
handlebars = "3.5.1"
|
||||
|
@ -28,7 +28,6 @@ pulldown-cmark = { version = "0.8.0", optional = true }
|
|||
rand = "0.8.3"
|
||||
regex = "1.4.1"
|
||||
rmp-serde = "0.15.5"
|
||||
rust-embed = "6.2.0"
|
||||
rusty_ulid = "0.10.1"
|
||||
serde = {version = "1.0.117", features = ["derive"]}
|
||||
serde_json = "1.0.60"
|
||||
|
|
|
@ -1,12 +1,126 @@
|
|||
#![warn (clippy::pedantic)]
|
||||
|
||||
use std::{
|
||||
iter::FromIterator,
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
use ptth_server::{
|
||||
load_toml,
|
||||
prelude::*,
|
||||
run_server,
|
||||
};
|
||||
|
||||
#[derive (Debug, StructOpt)]
|
||||
struct Opt {
|
||||
#[structopt (long)]
|
||||
auto_gen_key: bool,
|
||||
|
||||
#[structopt (long)]
|
||||
throttle_upload: bool,
|
||||
|
||||
#[structopt (long)]
|
||||
file_server_root: Option <PathBuf>,
|
||||
|
||||
#[structopt (long)]
|
||||
asset_root: Option <PathBuf>,
|
||||
|
||||
#[structopt (long)]
|
||||
config_path: Option <PathBuf>,
|
||||
|
||||
#[structopt (long)]
|
||||
name: Option <String>,
|
||||
|
||||
#[structopt (long)]
|
||||
print_tripcode: bool,
|
||||
|
||||
#[structopt (long)]
|
||||
relay_url: Option <String>,
|
||||
}
|
||||
|
||||
#[derive (Default, serde::Deserialize)]
|
||||
pub struct ConfigFile {
|
||||
pub name: Option <String>,
|
||||
pub api_key: String,
|
||||
pub relay_url: Option <String>,
|
||||
pub file_server_root: Option <PathBuf>,
|
||||
}
|
||||
|
||||
fn gen_and_save_key (path: &Path) -> anyhow::Result <()> {
|
||||
let api_key = ptth_core::gen_key ();
|
||||
|
||||
let mut f = File::create (path).with_context (|| format! ("Can't create config file `{:?}`", path))?;
|
||||
|
||||
#[cfg (unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let metadata = f.metadata ()?;
|
||||
let mut permissions = metadata.permissions ();
|
||||
permissions.set_mode (0o600);
|
||||
f.set_permissions (permissions)?;
|
||||
}
|
||||
#[cfg (not (unix))]
|
||||
{
|
||||
tracing::warn! ("Error VR6VW5QT: API keys aren't protected from clients on non-Unix OSes yet");
|
||||
}
|
||||
|
||||
f.write_all (format! ("api_key = \"{}\"\n", api_key).as_bytes ())?;
|
||||
|
||||
Ok (())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main () -> anyhow::Result <()> {
|
||||
async fn main () -> Result <(), anyhow::Error> {
|
||||
tracing_subscriber::fmt::init ();
|
||||
|
||||
let args = Vec::from_iter (std::env::args_os ());
|
||||
let opt = Opt::from_args ();
|
||||
let asset_root = opt.asset_root;
|
||||
|
||||
ptth_server::executable::main (&args).await
|
||||
let path = opt.config_path.clone ().unwrap_or_else (|| PathBuf::from ("./config/ptth_server.toml"));
|
||||
|
||||
let config_file: ConfigFile = if opt.auto_gen_key {
|
||||
// If we're in autonomous mode, try harder to fix things
|
||||
|
||||
match load_toml::load (&path) {
|
||||
Err (_) => {
|
||||
gen_and_save_key (&path)?;
|
||||
|
||||
load_toml::load (&path)?
|
||||
},
|
||||
Ok (x) => x,
|
||||
}
|
||||
}
|
||||
else {
|
||||
match load_toml::load (&path) {
|
||||
Err (ptth_server::errors::LoadTomlError::Io (_)) => bail! ("API key not provided in config file and auto-gen-key not provided"),
|
||||
Ok (x) => x,
|
||||
Err (e) => return Err (e.into ()),
|
||||
}
|
||||
};
|
||||
|
||||
let config_file = ptth_server::ConfigFile {
|
||||
name: opt.name.or (config_file.name).ok_or (anyhow::anyhow! ("`name` must be provided in command line or config file"))?,
|
||||
api_key: config_file.api_key,
|
||||
relay_url: opt.relay_url.or (config_file.relay_url).ok_or (anyhow::anyhow! ("`--relay-url` must be provided in command line or `relay_url` in config file"))?,
|
||||
file_server_root: opt.file_server_root.or (config_file.file_server_root),
|
||||
throttle_upload: opt.throttle_upload,
|
||||
};
|
||||
|
||||
if opt.print_tripcode {
|
||||
println! (r#"name = "{}""#, config_file.name);
|
||||
println! (r#"tripcode = "{}""#, config_file.tripcode ());
|
||||
return Ok (());
|
||||
}
|
||||
|
||||
run_server (
|
||||
config_file,
|
||||
ptth_core::graceful_shutdown::init (),
|
||||
Some (path),
|
||||
asset_root
|
||||
).await?;
|
||||
|
||||
Ok (())
|
||||
}
|
||||
|
|
|
@ -141,7 +141,6 @@ async fn serve_dir_json (
|
|||
|
||||
#[instrument (level = "debug", skip (f))]
|
||||
async fn serve_file (
|
||||
uri: &str,
|
||||
mut f: File,
|
||||
client_wants_body: bool,
|
||||
range: range::ValidParsed,
|
||||
|
@ -212,12 +211,6 @@ async fn serve_file (
|
|||
response.header (String::from ("content-range"), format! ("bytes {}-{}/{}", range.start, range.end - 1, range.end).into_bytes ());
|
||||
}
|
||||
|
||||
// Guess MIME type based on the URI so that we can serve web games
|
||||
|
||||
if uri.ends_with (".js") {
|
||||
response.header ("content-type".into (), b"application/javascript".to_vec ());
|
||||
}
|
||||
|
||||
response.content_length = Some (content_length);
|
||||
|
||||
if let Some (body) = body {
|
||||
|
@ -409,7 +402,7 @@ impl FileServer {
|
|||
file,
|
||||
send_body,
|
||||
range,
|
||||
}) => serve_file (uri, file.into_inner (), send_body, range, headers.get ("if-none-match").map (|v| &v[..])).await?,
|
||||
}) => serve_file (file.into_inner (), send_body, range, headers.get ("if-none-match").map (|v| &v[..])).await?,
|
||||
MarkdownErr (e) => {
|
||||
#[cfg (feature = "markdown")]
|
||||
{
|
||||
|
@ -436,26 +429,20 @@ impl FileServer {
|
|||
}
|
||||
|
||||
fn load_templates (
|
||||
_asset_root: &Path
|
||||
asset_root: &Path
|
||||
)
|
||||
-> anyhow::Result <Handlebars <'static>>
|
||||
-> Result <Handlebars <'static>, anyhow::Error>
|
||||
{
|
||||
use rust_embed::RustEmbed;
|
||||
#[derive (RustEmbed)]
|
||||
#[folder = "../../handlebars/server"]
|
||||
struct HandlebarsServer;
|
||||
|
||||
let mut handlebars = Handlebars::new ();
|
||||
handlebars.set_strict_mode (true);
|
||||
|
||||
let asset_root = asset_root.join ("handlebars/server");
|
||||
|
||||
for (k, v) in &[
|
||||
("file_server_dir", "file_server_dir.html"),
|
||||
("file_server_root", "file_server_root.html"),
|
||||
] {
|
||||
let asset_file = HandlebarsServer::get (v)
|
||||
.ok_or_else (|| anyhow::anyhow! ("failed to load handlebars template file"))?;
|
||||
let s = std::str::from_utf8 (asset_file.data.as_ref ())?;
|
||||
handlebars.register_template_string (k, s)?;
|
||||
handlebars.register_template_file (k, asset_root.join (v))?;
|
||||
}
|
||||
|
||||
Ok (handlebars)
|
||||
|
|
|
@ -39,17 +39,6 @@
|
|||
// False positive on futures::select! macro
|
||||
#![allow (clippy::mut_mut)]
|
||||
|
||||
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;
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
path::PathBuf,
|
||||
|
@ -69,10 +58,21 @@ use tokio_stream::wrappers::ReceiverStream;
|
|||
|
||||
use ptth_core::{
|
||||
http_serde,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
use errors::ServerError;
|
||||
use prelude::*;
|
||||
|
||||
pub struct State {
|
||||
// file_server: file_server::FileServer,
|
||||
|
@ -480,128 +480,6 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
pub mod executable {
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use super::{
|
||||
load_toml,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
pub async fn main (args: &[OsString]) -> anyhow::Result <()> {
|
||||
let opt = Opt::from_iter (args);
|
||||
let asset_root = opt.asset_root;
|
||||
|
||||
let path = opt.config_path.clone ().unwrap_or_else (|| PathBuf::from ("./config/ptth_server.toml"));
|
||||
|
||||
let config_file: ConfigFile = if opt.auto_gen_key {
|
||||
// If we're in autonomous mode, try harder to fix things
|
||||
|
||||
match load_toml::load (&path) {
|
||||
Err (_) => {
|
||||
gen_and_save_key (&path)?;
|
||||
|
||||
load_toml::load (&path)?
|
||||
},
|
||||
Ok (x) => x,
|
||||
}
|
||||
}
|
||||
else {
|
||||
match load_toml::load (&path) {
|
||||
Err (super::errors::LoadTomlError::Io (_)) => bail! ("API key not provided in config file and auto-gen-key not provided"),
|
||||
Ok (x) => x,
|
||||
Err (e) => return Err (e.into ()),
|
||||
}
|
||||
};
|
||||
|
||||
let config_file = super::ConfigFile {
|
||||
name: opt.name.or (config_file.name).ok_or (anyhow::anyhow! ("`name` must be provided in command line or config file"))?,
|
||||
api_key: config_file.api_key,
|
||||
relay_url: opt.relay_url.or (config_file.relay_url).ok_or (anyhow::anyhow! ("`--relay-url` must be provided in command line or `relay_url` in config file"))?,
|
||||
file_server_root: opt.file_server_root.or (config_file.file_server_root),
|
||||
throttle_upload: opt.throttle_upload,
|
||||
};
|
||||
|
||||
if opt.print_tripcode {
|
||||
println! (r#"name = "{}""#, config_file.name);
|
||||
println! (r#"tripcode = "{}""#, config_file.tripcode ());
|
||||
return Ok (());
|
||||
}
|
||||
|
||||
super::run_server (
|
||||
config_file,
|
||||
ptth_core::graceful_shutdown::init (),
|
||||
Some (path),
|
||||
asset_root
|
||||
).await?;
|
||||
|
||||
Ok (())
|
||||
}
|
||||
|
||||
#[derive (Debug, StructOpt)]
|
||||
struct Opt {
|
||||
#[structopt (long)]
|
||||
auto_gen_key: bool,
|
||||
|
||||
#[structopt (long)]
|
||||
throttle_upload: bool,
|
||||
|
||||
#[structopt (long)]
|
||||
file_server_root: Option <PathBuf>,
|
||||
|
||||
#[structopt (long)]
|
||||
asset_root: Option <PathBuf>,
|
||||
|
||||
#[structopt (long)]
|
||||
config_path: Option <PathBuf>,
|
||||
|
||||
#[structopt (long)]
|
||||
name: Option <String>,
|
||||
|
||||
#[structopt (long)]
|
||||
print_tripcode: bool,
|
||||
|
||||
#[structopt (long)]
|
||||
relay_url: Option <String>,
|
||||
}
|
||||
|
||||
#[derive (Default, serde::Deserialize)]
|
||||
struct ConfigFile {
|
||||
pub name: Option <String>,
|
||||
pub api_key: String,
|
||||
pub relay_url: Option <String>,
|
||||
pub file_server_root: Option <PathBuf>,
|
||||
}
|
||||
|
||||
fn gen_and_save_key (path: &Path) -> anyhow::Result <()> {
|
||||
use std::fs::File;
|
||||
|
||||
let api_key = ptth_core::gen_key ();
|
||||
|
||||
let mut f = File::create (path).with_context (|| format! ("Can't create config file `{:?}`", path))?;
|
||||
|
||||
#[cfg (unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let metadata = f.metadata ()?;
|
||||
let mut permissions = metadata.permissions ();
|
||||
permissions.set_mode (0o600);
|
||||
f.set_permissions (permissions)?;
|
||||
}
|
||||
#[cfg (not (unix))]
|
||||
{
|
||||
tracing::warn! ("Error VR6VW5QT: API keys aren't protected from clients on non-Unix OSes yet");
|
||||
}
|
||||
|
||||
f.write_all (format! ("api_key = \"{}\"\n", api_key).as_bytes ())?;
|
||||
|
||||
Ok (())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg (test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
pub use ptth_core::prelude::*;
|
||||
pub use std::{
|
||||
io::Write,
|
||||
};
|
||||
|
||||
pub use anyhow::{
|
||||
Context,
|
||||
bail,
|
||||
};
|
||||
|
|
|
@ -1,12 +1,107 @@
|
|||
use std::{
|
||||
iter::FromIterator,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use quic_demo::prelude::*;
|
||||
use protocol::PeerId;
|
||||
|
||||
#[derive (Debug, StructOpt)]
|
||||
struct Opt {
|
||||
#[structopt (long)]
|
||||
relay_addr: Option <String>,
|
||||
#[structopt (long)]
|
||||
server_id: Option <PeerId>,
|
||||
#[structopt (long)]
|
||||
debug_echo: bool,
|
||||
#[structopt (long)]
|
||||
cert_url: Option <String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main () -> anyhow::Result <()> {
|
||||
tracing_subscriber::fmt::init ();
|
||||
|
||||
let args = Vec::from_iter (std::env::args_os ());
|
||||
let opt = Arc::new (Opt::from_args ());
|
||||
|
||||
quic_demo::executable_end_server::main (&args).await
|
||||
let server_cert = match opt.cert_url.as_ref () {
|
||||
Some (url) => reqwest::get (url).await?.bytes ().await?,
|
||||
None => tokio::fs::read ("quic_server.crt").await?.into (),
|
||||
};
|
||||
let relay_addr = opt.relay_addr.clone ().unwrap_or_else (|| String::from ("127.0.0.1:30380")).parse ()?;
|
||||
let endpoint = make_client_endpoint ("0.0.0.0:0".parse ()?, &[&server_cert])?;
|
||||
|
||||
trace! ("Connecting to relay server");
|
||||
|
||||
let server_id = opt.server_id.clone ().unwrap_or_else (|| "bogus_server".to_string ());
|
||||
|
||||
let quinn::NewConnection {
|
||||
mut bi_streams,
|
||||
..
|
||||
} = protocol::p4_connect_to_p3 (&endpoint, &relay_addr, &server_id).await?;
|
||||
|
||||
debug! ("Connected to relay server");
|
||||
trace! ("Accepting bi streams from P3");
|
||||
|
||||
loop {
|
||||
let (relay_send, relay_recv) = bi_streams.next ().await.ok_or_else (|| anyhow::anyhow! ("Relay server didn't open a bi stream"))??;
|
||||
|
||||
tokio::spawn (handle_bi_stream (Arc::clone (&opt), relay_send, relay_recv));
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_bi_stream (
|
||||
opt: Arc <Opt>,
|
||||
relay_send: quinn::SendStream,
|
||||
mut relay_recv: quinn::RecvStream,
|
||||
) -> anyhow::Result <()>
|
||||
{
|
||||
match protocol::p4_accept_p3_stream (&mut relay_recv).await? {
|
||||
protocol::P3ToP4Stream::NewPtthConnection {
|
||||
client_id,
|
||||
..
|
||||
} => handle_new_ptth_connection (opt, relay_send, relay_recv, client_id).await?,
|
||||
}
|
||||
|
||||
Ok (())
|
||||
}
|
||||
|
||||
async fn handle_new_ptth_connection (
|
||||
opt: Arc <Opt>,
|
||||
mut relay_send: quinn::SendStream,
|
||||
mut relay_recv: quinn::RecvStream,
|
||||
_client_id: String,
|
||||
) -> anyhow::Result <()>
|
||||
{
|
||||
// TODO: Check authorization for P2 --> P4
|
||||
|
||||
protocol::p4_authorize_p2_connection (&mut relay_send).await?;
|
||||
let p4_to_p5_req = protocol::p4_expect_p5_request (&mut relay_recv).await?;
|
||||
|
||||
// TODO: Check authorization for P1 --> P5
|
||||
|
||||
protocol::p4_authorize_p1_connection (&mut relay_send).await?;
|
||||
|
||||
debug! ("Started PTTH connection");
|
||||
|
||||
if opt.debug_echo {
|
||||
relay_send.write (b"Connected to P4=P5 debug echo server\n").await?;
|
||||
debug! ("Relaying bytes using internal debug echo server (P4=P5)");
|
||||
tokio::io::copy (&mut relay_recv, &mut relay_send).await?;
|
||||
}
|
||||
else {
|
||||
let stream = TcpStream::connect (("127.0.0.1", p4_to_p5_req.port)).await?;
|
||||
let (local_recv, local_send) = stream.into_split ();
|
||||
|
||||
trace! ("Relaying bytes...");
|
||||
|
||||
let ptth_conn = quic_demo::connection::NewConnection {
|
||||
local_send,
|
||||
local_recv,
|
||||
relay_send,
|
||||
relay_recv,
|
||||
}.build ();
|
||||
|
||||
ptth_conn.wait_for_close ().await?;
|
||||
}
|
||||
|
||||
Ok (())
|
||||
}
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
use structopt::StructOpt;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::prelude::*;
|
||||
use protocol::PeerId;
|
||||
|
||||
#[derive (Debug, StructOpt)]
|
||||
struct Opt {
|
||||
#[structopt (long)]
|
||||
relay_addr: Option <String>,
|
||||
#[structopt (long)]
|
||||
server_id: Option <PeerId>,
|
||||
#[structopt (long)]
|
||||
debug_echo: bool,
|
||||
#[structopt (long)]
|
||||
cert_url: Option <String>,
|
||||
}
|
||||
|
||||
pub async fn main (args: &[OsString]) -> anyhow::Result <()> {
|
||||
let opt = Arc::new (Opt::from_iter (args));
|
||||
|
||||
let server_cert = match opt.cert_url.as_ref () {
|
||||
Some (url) => reqwest::get (url).await?.bytes ().await?,
|
||||
None => tokio::fs::read ("quic_server.crt").await?.into (),
|
||||
};
|
||||
let relay_addr = opt.relay_addr.clone ().unwrap_or_else (|| String::from ("127.0.0.1:30380")).parse ()?;
|
||||
let endpoint = make_client_endpoint ("0.0.0.0:0".parse ()?, &[&server_cert])?;
|
||||
|
||||
trace! ("Connecting to relay server");
|
||||
|
||||
let server_id = opt.server_id.clone ().unwrap_or_else (|| "bogus_server".to_string ());
|
||||
|
||||
let quinn::NewConnection {
|
||||
mut bi_streams,
|
||||
..
|
||||
} = protocol::p4_connect_to_p3 (&endpoint, &relay_addr, &server_id).await?;
|
||||
|
||||
debug! ("Connected to relay server");
|
||||
trace! ("Accepting bi streams from P3");
|
||||
|
||||
loop {
|
||||
let (relay_send, relay_recv) = bi_streams.next ().await.ok_or_else (|| anyhow::anyhow! ("Relay server didn't open a bi stream"))??;
|
||||
|
||||
tokio::spawn (handle_bi_stream (Arc::clone (&opt), relay_send, relay_recv));
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_bi_stream (
|
||||
opt: Arc <Opt>,
|
||||
relay_send: quinn::SendStream,
|
||||
mut relay_recv: quinn::RecvStream,
|
||||
) -> anyhow::Result <()>
|
||||
{
|
||||
match protocol::p4_accept_p3_stream (&mut relay_recv).await? {
|
||||
protocol::P3ToP4Stream::NewPtthConnection {
|
||||
client_id,
|
||||
..
|
||||
} => handle_new_ptth_connection (opt, relay_send, relay_recv, client_id).await?,
|
||||
}
|
||||
|
||||
Ok (())
|
||||
}
|
||||
|
||||
async fn handle_new_ptth_connection (
|
||||
opt: Arc <Opt>,
|
||||
mut relay_send: quinn::SendStream,
|
||||
mut relay_recv: quinn::RecvStream,
|
||||
_client_id: String,
|
||||
) -> anyhow::Result <()>
|
||||
{
|
||||
// TODO: Check authorization for P2 --> P4
|
||||
|
||||
protocol::p4_authorize_p2_connection (&mut relay_send).await?;
|
||||
let p4_to_p5_req = protocol::p4_expect_p5_request (&mut relay_recv).await?;
|
||||
|
||||
// TODO: Check authorization for P1 --> P5
|
||||
|
||||
protocol::p4_authorize_p1_connection (&mut relay_send).await?;
|
||||
|
||||
debug! ("Started PTTH connection");
|
||||
|
||||
if opt.debug_echo {
|
||||
relay_send.write (b"Connected to P4=P5 debug echo server\n").await?;
|
||||
debug! ("Relaying bytes using internal debug echo server (P4=P5)");
|
||||
tokio::io::copy (&mut relay_recv, &mut relay_send).await?;
|
||||
}
|
||||
else {
|
||||
let stream = TcpStream::connect (("127.0.0.1", p4_to_p5_req.port)).await?;
|
||||
let (local_recv, local_send) = stream.into_split ();
|
||||
|
||||
trace! ("Relaying bytes...");
|
||||
|
||||
let ptth_conn = crate::connection::NewConnection {
|
||||
local_send,
|
||||
local_recv,
|
||||
relay_send,
|
||||
relay_recv,
|
||||
}.build ();
|
||||
|
||||
ptth_conn.wait_for_close ().await?;
|
||||
}
|
||||
|
||||
Ok (())
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
pub mod client_proxy;
|
||||
pub mod connection;
|
||||
pub mod executable_end_server;
|
||||
pub mod prelude;
|
||||
pub mod protocol;
|
||||
pub mod quinn_utils;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
pub use std::{
|
||||
collections::*,
|
||||
ffi::OsString,
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
Arc,
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
![The PTTH logo, a green box sitting on a black conveyor belt. The box has an arrow pointing left, and the text "PTTH", in white. The conveyor belt has an arrow pointing right, in white.](assets/logo-128-pixel.png)
|
||||
|
||||
TODO: "Splitting a server in half" diagram
|
||||
|
||||
# PTTH
|
||||
|
||||
PTTH lets you run file servers from behind NAT / firewalls.
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.55.0
|
||||
1.50.0
|
||||
|
|
Loading…
Reference in New Issue