use anyhow for error handling

This commit is contained in:
Dmitry Belyaev 2023-08-17 23:09:44 +03:00
parent 376c2f860c
commit a369fc5aee
Signed by: b4tman
GPG Key ID: 41A00BF15EA7E5F3
6 changed files with 70 additions and 85 deletions

5
Cargo.lock generated
View File

@ -92,9 +92,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.74" version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]] [[package]]
name = "async-stream" name = "async-stream"
@ -658,6 +658,7 @@ dependencies = [
name = "socks5ws" name = "socks5ws"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"async-stream", "async-stream",
"clap", "clap",
"ctrlc", "ctrlc",

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.75"
async-stream = "0.3.3" async-stream = "0.3.3"
clap = { version = "4.3.21", features = ["derive"] } clap = { version = "4.3.21", features = ["derive"] }
ctrlc = "3.2.3" ctrlc = "3.2.3"

View File

@ -3,6 +3,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use anyhow::{Context, Result};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
@ -65,25 +66,24 @@ impl Default for Config {
impl Config { impl Config {
const FILENAME: &'static str = "config.toml"; const FILENAME: &'static str = "config.toml";
fn read<P: AsRef<Path>>(path: P) -> Result<Self, String> { fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
let data = fs::read_to_string(path).map_err(|e| format!("can't read config: {:?}", e))?; let data = fs::read_to_string(path).context("can't read config")?;
toml::from_str(&data).map_err(|e| format!("can't parse config: {:?}", e)) toml::from_str(&data).context("can't parse config")
} }
fn write<P: AsRef<Path>>(&self, path: P) -> Result<(), String> { fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let data = toml::to_string_pretty(&self) let data = toml::to_string_pretty(&self).context("can't serialize config")?;
.map_err(|e| format!("can't serialize config: {:?}", e))?; fs::write(path, data).context("can't write config")
fs::write(path, data).map_err(|e| format!("can't write config: {:?}", e))
} }
fn file_location() -> Result<PathBuf, String> { fn file_location() -> Result<PathBuf> {
let res = env::current_exe() let res = env::current_exe()
.map_err(|e| format!("can't get current exe path: {:?}", e))? .context("can't get current exe path")?
.with_file_name(Config::FILENAME); .with_file_name(Config::FILENAME);
Ok(res) Ok(res)
} }
pub fn get() -> Self { pub fn get() -> Self {
let path = Config::file_location(); let path = Config::file_location();
if let Err(e) = path { if let Err(e) = path {
log::error!("Error: {e}, using default config"); log::error!(r#"Error: "{e}", using default config"#);
return Config::default(); return Config::default();
} }
@ -91,7 +91,7 @@ impl Config {
let cfg = Config::read(path); let cfg = Config::read(path);
match cfg { match cfg {
Err(e) => { Err(e) => {
log::error!("Error: {e}, using default config"); log::error!(r#"Error: "{e}", using default config"#);
Config::default() Config::default()
} }
Ok(cfg) => cfg, Ok(cfg) => cfg,
@ -106,9 +106,9 @@ impl Config {
let res = self.write(&path); let res = self.write(&path);
if let Err(e) = res { if let Err(e) = res {
log::error!("save error: {e}"); log::error!("save error: {}", &e);
} else { } else {
log::info!("config saved to: {}", path.to_str().unwrap()); log::info!(r#"config saved to: "{}""#, path.to_str().unwrap());
} }
} }
} }

View File

@ -9,6 +9,7 @@ use flexi_logger::{
AdaptiveFormat, Age, Cleanup, Criterion, Duplicate, FileSpec, Logger, LoggerHandle, Naming, AdaptiveFormat, Age, Cleanup, Criterion, Duplicate, FileSpec, Logger, LoggerHandle, Naming,
}; };
use anyhow::{Context, Result};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@ -39,15 +40,15 @@ struct Cli {
command: Command, command: Command,
} }
fn create_logger() -> LoggerHandle { fn create_logger() -> Result<LoggerHandle> {
Logger::try_with_str("info") Logger::try_with_str("info")
.expect("default logging level invalid") .context("default logging level invalid")?
.log_to_file( .log_to_file(
FileSpec::default().directory( FileSpec::default().directory(
std::env::current_exe() std::env::current_exe()
.expect("can't get current exe path") .context("can't get current exe path")?
.parent() .parent()
.expect("can't get parent folder"), .context("can't get parent folder")?,
), ),
) )
.rotate( .rotate(
@ -62,13 +63,18 @@ fn create_logger() -> LoggerHandle {
.write_mode(flexi_logger::WriteMode::Async) .write_mode(flexi_logger::WriteMode::Async)
.start_with_specfile( .start_with_specfile(
std::env::current_exe() std::env::current_exe()
.unwrap() .context("can't get current exe path")?
.with_file_name("logspec.toml"), .with_file_name("logspec.toml"),
) )
.expect("can't start logger") .context("can't start logger")
} }
fn server_foreground() { fn save_default_config() -> Result<()> {
Config::default().save();
Ok(())
}
fn server_foreground() -> Result<()> {
let control_token = CancellationToken::new(); let control_token = CancellationToken::new();
let server_token = control_token.child_token(); let server_token = control_token.child_token();
@ -81,12 +87,14 @@ fn server_foreground() {
log::info!("Press Ctrl-C to stop server"); log::info!("Press Ctrl-C to stop server");
} }
server::server_executor(Config::get(), server_token).unwrap(); server::server_executor(Config::get(), server_token)?;
Ok(())
} }
fn main() { fn main() -> Result<()> {
let args = Cli::parse(); let args = Cli::parse();
let logger = create_logger(); let logger = create_logger()?;
let res = match args.command { let res = match args.command {
Command::Install => service::install(), Command::Install => service::install(),
@ -94,19 +102,15 @@ fn main() {
Command::Run => service::run(), Command::Run => service::run(),
Command::Start => service::start(), Command::Start => service::start(),
Command::Stop => service::stop(), Command::Stop => service::stop(),
Command::SaveConfig => { Command::SaveConfig => save_default_config(),
Config::default().save(); Command::Serve => server_foreground(),
Ok(())
}
Command::Serve => {
server_foreground();
Ok(())
}
}; };
if let Err(e) = res { if let Err(e) = &res {
log::error!("{:?} error: {:#?}", args.command, e); log::error!("{:?} -> error: {:?}", args.command, e);
} }
res?;
drop(logger); drop(logger);
Ok(())
} }

View File

@ -1,9 +1,7 @@
use fast_socks5::{ use anyhow::{anyhow, Result};
server::{SimpleUserPassword, Socks5Server, Socks5Socket}, use fast_socks5::server::{SimpleUserPassword, Socks5Server, Socks5Socket};
Result,
};
use std::future::Future; use std::future::Future;
use tokio::io::{self, AsyncRead, AsyncWrite}; use tokio::io::{AsyncRead, AsyncWrite};
use tokio::select; use tokio::select;
use tokio::task; use tokio::task;
use tokio_stream::{Stream, StreamExt}; use tokio_stream::{Stream, StreamExt};
@ -14,14 +12,14 @@ use async_stream::stream;
use crate::config::Config; use crate::config::Config;
use crate::config::PasswordAuth; use crate::config::PasswordAuth;
pub fn server_executor(cfg: Config, token: CancellationToken) -> io::Result<()> { pub fn server_executor(cfg: Config, token: CancellationToken) -> Result<()> {
tokio::runtime::Builder::new_multi_thread() tokio::runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
.build()? .build()?
.block_on(async { spawn_socks5_server(cfg, token).await }) .block_on(async { spawn_socks5_server(cfg, token).await })
} }
pub async fn spawn_socks5_server(cfg: Config, token: CancellationToken) -> io::Result<()> { pub async fn spawn_socks5_server(cfg: Config, token: CancellationToken) -> Result<()> {
let mut server_config = fast_socks5::server::Config::default(); let mut server_config = fast_socks5::server::Config::default();
server_config.set_request_timeout(cfg.request_timeout); server_config.set_request_timeout(cfg.request_timeout);
server_config.set_skip_auth(cfg.skip_auth); server_config.set_skip_auth(cfg.skip_auth);
@ -51,7 +49,7 @@ pub async fn spawn_socks5_server(cfg: Config, token: CancellationToken) -> io::R
spawn_and_log_error(socket.upgrade_to_socks5(), child_token); spawn_and_log_error(socket.upgrade_to_socks5(), child_token);
} }
Err(err) => { Err(err) => {
log::error!("accept error = {:?}", err); log::error!("accept error: {}", err);
} }
} }
} }
@ -93,7 +91,7 @@ where
fn spawn_and_log_error<F, T>(future: F, token: CancellationToken) -> task::JoinHandle<()> fn spawn_and_log_error<F, T>(future: F, token: CancellationToken) -> task::JoinHandle<()>
where where
F: Future<Output = Result<Socks5Socket<T>>> + Send + 'static, F: Future<Output = fast_socks5::Result<Socks5Socket<T>>> + Send + 'static,
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
{ {
tokio::spawn(async move { tokio::spawn(async move {
@ -101,10 +99,10 @@ where
biased; biased;
_ = token.cancelled() => { _ = token.cancelled() => {
Err("Client connection canceled".to_string()) Err(anyhow!("Client connection canceled"))
} }
res = future => { res = future => {
res.map_err(|e| e.to_string()) res.map_err(anyhow::Error::new)
} }
}; };
if let Err(e) = result { if let Err(e) = result {

View File

@ -1,5 +1,6 @@
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use anyhow::{anyhow, Result};
use std::{ffi::OsString, thread, time::Duration}; use std::{ffi::OsString, thread, time::Duration};
use windows_service::{ use windows_service::{
define_windows_service, define_windows_service,
@ -10,7 +11,6 @@ use windows_service::{
service_control_handler::{self, ServiceControlHandlerResult}, service_control_handler::{self, ServiceControlHandlerResult},
service_dispatcher, service_dispatcher,
service_manager::{ServiceManager, ServiceManagerAccess}, service_manager::{ServiceManager, ServiceManagerAccess},
Result,
}; };
use crate::config::Config; use crate::config::Config;
@ -21,21 +21,6 @@ const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
const SERVICE_DISPLAY: &str = "socks5ws proxy"; const SERVICE_DISPLAY: &str = "socks5ws proxy";
const SERVICE_DESCRIPTION: &str = "SOCKS5 proxy windows service"; const SERVICE_DESCRIPTION: &str = "SOCKS5 proxy windows service";
trait ErrorToString {
type Output;
fn str_err(self) -> std::result::Result<Self::Output, String>;
}
impl<T, E> ErrorToString for std::result::Result<T, E>
where
E: std::error::Error,
{
type Output = T;
fn str_err(self) -> std::result::Result<Self::Output, String> {
self.map_err(|e| e.to_string())
}
}
trait ServiceStatusEx { trait ServiceStatusEx {
fn running() -> ServiceStatus; fn running() -> ServiceStatus;
fn stopped() -> ServiceStatus; fn stopped() -> ServiceStatus;
@ -71,11 +56,11 @@ impl ServiceStatusEx for ServiceStatus {
} }
} }
pub fn install() -> windows_service::Result<()> { pub fn install() -> Result<()> {
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE; let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?; let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
let service_binary_path = ::std::env::current_exe().unwrap(); let service_binary_path = std::env::current_exe()?;
let service_info = ServiceInfo { let service_info = ServiceInfo {
name: SERVICE_NAME.into(), name: SERVICE_NAME.into(),
@ -95,7 +80,7 @@ pub fn install() -> windows_service::Result<()> {
Ok(()) Ok(())
} }
pub fn uninstall() -> windows_service::Result<()> { pub fn uninstall() -> Result<()> {
let manager_access = ServiceManagerAccess::CONNECT; let manager_access = ServiceManagerAccess::CONNECT;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?; let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
@ -115,7 +100,7 @@ pub fn uninstall() -> windows_service::Result<()> {
Ok(()) Ok(())
} }
pub fn stop() -> windows_service::Result<()> { pub fn stop() -> Result<()> {
let manager_access = ServiceManagerAccess::CONNECT; let manager_access = ServiceManagerAccess::CONNECT;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?; let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
@ -130,7 +115,7 @@ pub fn stop() -> windows_service::Result<()> {
Ok(()) Ok(())
} }
pub fn start() -> windows_service::Result<()> { pub fn start() -> Result<()> {
let manager_access = ServiceManagerAccess::CONNECT; let manager_access = ServiceManagerAccess::CONNECT;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?; let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
@ -149,7 +134,9 @@ pub fn run() -> Result<()> {
// Register generated `ffi_service_main` with the system and start the service, blocking // Register generated `ffi_service_main` with the system and start the service, blocking
// this thread until the service is stopped. // this thread until the service is stopped.
log::info!("service run"); log::info!("service run");
service_dispatcher::start(SERVICE_NAME, ffi_service_main) service_dispatcher::start(SERVICE_NAME, ffi_service_main)?;
Ok(())
} }
// Generate the windows service boilerplate. // Generate the windows service boilerplate.
@ -163,11 +150,11 @@ define_windows_service!(ffi_service_main, my_service_main);
// output to file if needed. // output to file if needed.
pub fn my_service_main(_arguments: Vec<OsString>) { pub fn my_service_main(_arguments: Vec<OsString>) {
if let Err(e) = run_service() { if let Err(e) = run_service() {
log::error!("error: {:#?}", e); log::error!("error: {}", e);
} }
} }
pub fn run_service() -> std::result::Result<(), String> { pub fn run_service() -> Result<()> {
// Create a cancellation token to be able to cancell server // Create a cancellation token to be able to cancell server
let control_token = CancellationToken::new(); let control_token = CancellationToken::new();
let server_token = control_token.child_token(); let server_token = control_token.child_token();
@ -192,12 +179,10 @@ pub fn run_service() -> std::result::Result<(), String> {
// Register system service event handler. // Register system service event handler.
// The returned status handle should be used to report service status changes to the system. // The returned status handle should be used to report service status changes to the system.
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler).str_err()?; let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
// Tell the system that service is running // Tell the system that service is running
status_handle status_handle.set_service_status(ServiceStatus::running())?;
.set_service_status(ServiceStatus::running())
.str_err()?;
let cfg = Config::get(); let cfg = Config::get();
log::info!("start with config: {:#?}", cfg); log::info!("start with config: {:#?}", cfg);
@ -206,26 +191,22 @@ pub fn run_service() -> std::result::Result<(), String> {
log::info!("server thread stoped"); log::info!("server thread stoped");
// join() => Err(), when thread panic
if let Err(e) = result { if let Err(e) = result {
log::error!("server panic: {:#?}", e); log::error!("server panic: {:#?}", e);
status_handle status_handle.set_service_status(ServiceStatus::stopped_with_error(1))?;
.set_service_status(ServiceStatus::stopped_with_error(1)) return Err(anyhow!("server panic"));
.str_err()?;
return Err("server panic".into());
} }
// join() => Ok(Err()), when server executor error
if let Err(e) = result.unwrap() { if let Err(e) = result.unwrap() {
log::error!("server error: {:#?}", e); log::error!("server error: {:#?}", e);
status_handle status_handle.set_service_status(ServiceStatus::stopped_with_error(2))?;
.set_service_status(ServiceStatus::stopped_with_error(2)) return Err(anyhow!("server error"));
.str_err()?;
return Err("server error".into());
} }
// Tell the system that service has stopped. // Tell the system that service has stopped.
status_handle status_handle.set_service_status(ServiceStatus::stopped())?;
.set_service_status(ServiceStatus::stopped())
.str_err()?;
log::info!("service stoped"); log::info!("service stoped");
Ok(()) Ok(())