chore: great error handling
This commit is contained in:
parent
db5247c713
commit
716707f1bc
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -52,6 +52,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -327,6 +333,26 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.23"
|
version = "0.8.23"
|
||||||
@ -373,12 +399,14 @@ name = "trpha"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"httparse",
|
"httparse",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
"shlex",
|
"shlex",
|
||||||
|
"thiserror",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anstyle = "1"
|
anstyle = "1"
|
||||||
|
anyhow = "1"
|
||||||
clap = { version = "4", features = ["derive", "string"] }
|
clap = { version = "4", features = ["derive", "string"] }
|
||||||
ctrlc = "3"
|
ctrlc = "3"
|
||||||
httparse = "1"
|
httparse = "1"
|
||||||
parking_lot = { version = "0", features = ["arc_lock", "serde"] }
|
parking_lot = { version = "0", features = ["arc_lock", "serde"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
shlex = "1"
|
shlex = "1"
|
||||||
|
thiserror = "2"
|
||||||
toml = "0"
|
toml = "0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
1
src/constants.rs
Normal file
1
src/constants.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub const DEFAULT_CONFIG: &[u8] = include_bytes!("../config.default.toml");
|
@ -8,15 +8,17 @@ use std::{
|
|||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use parking_lot::RwLockWriteGuard;
|
use parking_lot::RwLockWriteGuard;
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
|
|
||||||
pub fn start_client(config: config::Schema) {
|
// TODO: make more anyhow
|
||||||
let mut stream = UnixStream::connect(config.ipc.expect("no ipc socket specified"))
|
pub fn start_client(config: config::Schema) -> anyhow::Result<()> {
|
||||||
.expect("failed to connect to unix socket");
|
let mut stream = UnixStream::connect(config.ipc.context("no ipc socket specified")?)
|
||||||
|
.context("failed to connect to unix socket")?;
|
||||||
|
|
||||||
let stream2 = stream.try_clone().expect("failed to clone stream");
|
let stream2 = stream.try_clone().context("failed to clone stream")?;
|
||||||
let mut reader = BufReader::new(stream2);
|
let mut reader = BufReader::new(stream2);
|
||||||
|
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
@ -32,7 +34,7 @@ pub fn start_client(config: config::Schema) {
|
|||||||
|
|
||||||
for line in (&mut reader).lines() {
|
for line in (&mut reader).lines() {
|
||||||
let Ok(line) = line else {
|
let Ok(line) = line else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
if line == "EOF" {
|
if line == "EOF" {
|
||||||
break;
|
break;
|
||||||
@ -43,9 +45,11 @@ pub fn start_client(config: config::Schema) {
|
|||||||
print!("> ");
|
print!("> ");
|
||||||
_ = stdout().flush();
|
_ = stdout().flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_daemon_client(mut stream: UnixStream, config: Arc<config::Schema>) {
|
pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc<config::Schema>) {
|
||||||
let Ok(stream2) = stream.try_clone() else {
|
let Ok(stream2) = stream.try_clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -68,13 +72,11 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: Arc<config::Schema>)
|
|||||||
_ = writeln!(
|
_ = writeln!(
|
||||||
stream,
|
stream,
|
||||||
"weak {}, strong {}",
|
"weak {}, strong {}",
|
||||||
Arc::weak_count(&config),
|
Arc::weak_count(config),
|
||||||
Arc::strong_count(&config)
|
Arc::strong_count(config)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"confdump" => {
|
"confdump" => _ = writeln!(stream, "{:#?}", config.as_ref()),
|
||||||
_ = writeln!(stream, "{:#?}", config.as_ref());
|
|
||||||
}
|
|
||||||
"hosts" => {
|
"hosts" => {
|
||||||
if let [arg0, args @ ..] = args {
|
if let [arg0, args @ ..] = args {
|
||||||
match arg0.as_str() {
|
match arg0.as_str() {
|
||||||
@ -107,13 +109,13 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: Arc<config::Schema>)
|
|||||||
"err: parsing value as socket addres {err:?}"
|
"err: parsing value as socket addres {err:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = writeln!(
|
_ = writeln!(
|
||||||
stream,
|
stream,
|
||||||
"err: value wasn't 3 comma separated values"
|
"err: value wasn't 3 comma separated values"
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = writeln!(
|
_ = writeln!(
|
||||||
stream,
|
stream,
|
||||||
@ -136,43 +138,40 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: Arc<config::Schema>)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = writeln!(stream, "err: not enough arguments");
|
_ = writeln!(stream, "err: not enough arguments");
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
_ = writeln!(stream, "command {arg0:?} doesn't exist, type \"help\"");
|
_ = writeln!(stream, "command {arg0:?} doesn't exist, type \"help\"");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
_ = stream.write_all(b"EOF\n"); // BEL char
|
_ = stream.write_all(b"EOF\n"); // BEL char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_daemon(config: Arc<config::Schema>) -> Option<JoinHandle<()>> {
|
pub fn handle_daemon(config: Arc<config::Schema>) -> Option<anyhow::Result<JoinHandle<()>>> {
|
||||||
if let Some(ipc_path) = config.ipc.as_ref() {
|
config.ipc.clone().map(|ipc_path| {
|
||||||
println!("starting ipc daemon at {ipc_path:#?}");
|
println!("starting ipc daemon at {ipc_path:#?}");
|
||||||
|
|
||||||
let listener = UnixListener::bind(ipc_path.clone()).expect("failed to bind to ipc socket");
|
let listener = UnixListener::bind(&ipc_path).context("failed to bind to ipc socket")?;
|
||||||
let ipc_path_clone = ipc_path.clone();
|
|
||||||
ctrlc::try_set_handler(move || {
|
ctrlc::try_set_handler(move || {
|
||||||
_ = fs::remove_file(&ipc_path_clone);
|
_ = fs::remove_file(&ipc_path);
|
||||||
process::exit(2);
|
process::exit(2);
|
||||||
})
|
})
|
||||||
.expect("failed to set exit handler");
|
.context("failed to set exit handler")?;
|
||||||
|
|
||||||
println!("ipc daemon started");
|
println!("ipc daemon started");
|
||||||
|
|
||||||
Some(thread::spawn(move || {
|
Ok(thread::spawn(move || {
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
match stream {
|
match stream {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
handle_daemon_client(stream, config.clone());
|
handle_daemon_client(stream, &config);
|
||||||
}
|
}
|
||||||
Err(err) => eprintln!("ipc daemon error: {err:#?}"),
|
Err(err) => eprintln!("ipc daemon error: {err:#?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
})
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
82
src/main.rs
82
src/main.rs
@ -1,10 +1,11 @@
|
|||||||
//! # Tuxcord Reverse Proxy Header Authenthication
|
//! # Tuxcord Reverse Proxy Header Authenthication
|
||||||
#![feature(
|
#![feature(
|
||||||
rwlock_downgrade,
|
anonymous_lifetime_in_impl_trait,
|
||||||
try_blocks,
|
|
||||||
iterator_try_collect,
|
iterator_try_collect,
|
||||||
anonymous_lifetime_in_impl_trait
|
rwlock_downgrade,
|
||||||
|
try_blocks
|
||||||
)]
|
)]
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -14,20 +15,22 @@ use std::{
|
|||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::utils::headers::HeadersExt;
|
use crate::utils::headers::HeadersExt;
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod constants;
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use args::Args;
|
use args::Args;
|
||||||
|
|
||||||
const DEFAULT_CONFIG: &[u8] = include_bytes!("../config.default.toml");
|
fn main() -> anyhow::Result<()> {
|
||||||
fn main() {
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
if !args.config.exists() {
|
if !args.config.exists() {
|
||||||
@ -36,27 +39,27 @@ fn main() {
|
|||||||
&args.config
|
&args.config
|
||||||
);
|
);
|
||||||
File::create(args.config)
|
File::create(args.config)
|
||||||
.expect("failure creating the config file")
|
.context("failure creating the config file")?
|
||||||
.write_all(DEFAULT_CONFIG)
|
.write_all(constants::DEFAULT_CONFIG)
|
||||||
.expect("failure writting the contents to the config file");
|
.context("failure writting the contents to the config file")?;
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config_file = File::open(&args.config).expect("failure opening the config file");
|
let mut config_file = File::open(&args.config).context("failure opening the config file")?;
|
||||||
let mut config = String::new();
|
let mut config = String::new();
|
||||||
config_file
|
config_file
|
||||||
.read_to_string(&mut config)
|
.read_to_string(&mut config)
|
||||||
.expect("failure reading the config file");
|
.context("failure reading the config file")?;
|
||||||
let config: config::Schema = toml::from_str(&config).expect("invalid config file");
|
let config: config::Schema = toml::from_str(&config).context("invalid config file")?;
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
if let Some(args::Commands::Ipc) = args.command {
|
if let Some(args::Commands::Ipc) = args.command {
|
||||||
ipc::start_client(config);
|
ipc::start_client(config)?;
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("config: {config:#?}");
|
println!("config: {config:#?}");
|
||||||
let listener = TcpListener::bind(config.listen_at).expect("failure tcp listening");
|
let listener = TcpListener::bind(config.listen_at).context("failure tcp listening")?;
|
||||||
let config_arc = Arc::new(config); // will also serve as a counter
|
let config_arc = Arc::new(config); // will also serve as a counter
|
||||||
|
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
@ -68,7 +71,7 @@ fn main() {
|
|||||||
let config_arc = config_arc.clone();
|
let config_arc = config_arc.clone();
|
||||||
thread::spawn(|| {
|
thread::spawn(|| {
|
||||||
if let Err(err) = handle_client(&mut client, config_arc.as_ref()) {
|
if let Err(err) = handle_client(&mut client, config_arc.as_ref()) {
|
||||||
eprintln!("err: invalid req head ({err:?}), closing...");
|
eprintln!("err: invalid req head ({err}), closing...");
|
||||||
_ = client.shutdown(Shutdown::Both);
|
_ = client.shutdown(Shutdown::Both);
|
||||||
}
|
}
|
||||||
drop(client);
|
drop(client);
|
||||||
@ -82,7 +85,23 @@ fn main() {
|
|||||||
unreachable!("listener had to be killed unexpectedly");
|
unreachable!("listener had to be killed unexpectedly");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), &'static str> {
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ClientError {
|
||||||
|
#[error("error reading request head: {0}")]
|
||||||
|
HeadReadError(io::Error),
|
||||||
|
#[error("failed to find \"host\" header")]
|
||||||
|
HostHeaderNotFound,
|
||||||
|
#[error("requested host {0:?}, but it's not registered")]
|
||||||
|
HostNotRegistered(String),
|
||||||
|
#[error("failed to connect to backend: {0}")]
|
||||||
|
BackendConnectFail(io::Error),
|
||||||
|
#[error("io error exchanging client and backend: {0}")]
|
||||||
|
ExchangeIoError(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), ClientError> {
|
||||||
|
use ClientError as E;
|
||||||
|
|
||||||
let mut header_buf = [0u8; 1024 * 8];
|
let mut header_buf = [0u8; 1024 * 8];
|
||||||
let mut read_pos = 0usize;
|
let mut read_pos = 0usize;
|
||||||
|
|
||||||
@ -91,9 +110,9 @@ fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(),
|
|||||||
headers = [httparse::EMPTY_HEADER; 16];
|
headers = [httparse::EMPTY_HEADER; 16];
|
||||||
let mut req = httparse::Request::new(&mut headers);
|
let mut req = httparse::Request::new(&mut headers);
|
||||||
|
|
||||||
let Ok(n) = client.read(&mut header_buf[read_pos..]) else {
|
let n = client
|
||||||
return Err("error reading stream");
|
.read(&mut header_buf[read_pos..])
|
||||||
};
|
.map_err(E::HeadReadError)?;
|
||||||
read_pos += n;
|
read_pos += n;
|
||||||
|
|
||||||
if let Ok(httparse::Status::Complete(n)) = req.parse(&header_buf[0..read_pos]) {
|
if let Ok(httparse::Status::Complete(n)) = req.parse(&header_buf[0..read_pos]) {
|
||||||
@ -103,31 +122,28 @@ fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(),
|
|||||||
|
|
||||||
let theres_body = req.headers.has_any(["conten-length", "transfer-encoding"]);
|
let theres_body = req.headers.has_any(["conten-length", "transfer-encoding"]);
|
||||||
|
|
||||||
let Some(host_header) = req.headers.get("host") else {
|
let host_header = req.headers.get("host").ok_or(E::HostHeaderNotFound)?;
|
||||||
return Err("failed to find \"host\" header");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Now find that header and pas everything
|
// Now find that header and pas everything
|
||||||
let read_hosts = config.hosts.read();
|
let read_hosts = config.hosts.read();
|
||||||
let Some((addr, _, _)) = read_hosts.get(host_header.as_ref()) else {
|
let (addr, _, _) = read_hosts
|
||||||
return Err("host not in hashmap");
|
.get(host_header.as_ref())
|
||||||
};
|
.ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?;
|
||||||
|
|
||||||
let Ok(mut stream) = TcpStream::connect(addr) else {
|
let mut stream = TcpStream::connect(addr).map_err(E::BackendConnectFail)?;
|
||||||
return Err("failed to connect to the hashmap address");
|
|
||||||
};
|
|
||||||
drop(read_hosts);
|
drop(read_hosts);
|
||||||
let Ok(_): io::Result<()> = (try {
|
let r: io::Result<()> = try {
|
||||||
stream.write_all(&header_buf[0..pos])?;
|
stream.write_all(&header_buf[0..pos])?;
|
||||||
// here we sent all original headers, append our own (TODO)
|
// here we sent all original headers, append our own (TODO)
|
||||||
stream.write_all(&header_buf[pos..read_pos])?; // send our overhead
|
|
||||||
|
// send our overhead
|
||||||
|
stream.write_all(&header_buf[pos..read_pos])?;
|
||||||
if theres_body {
|
if theres_body {
|
||||||
io::copy(client, &mut stream)?;
|
io::copy(client, &mut stream)?;
|
||||||
}
|
}
|
||||||
io::copy(&mut stream, client)?;
|
io::copy(&mut stream, client)?;
|
||||||
}) else {
|
|
||||||
return Err("io error exchanging head and/or body");
|
|
||||||
};
|
};
|
||||||
|
r.map_err(E::ExchangeIoError)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user