use clap::Parser;
use tracing::{error, info, warn};
use tracing_subscriber::EnvFilter;
use tiny_proxy::cli::Cli;
use tiny_proxy::config::Config;
#[cfg(feature = "api")]
use std::sync::Arc;
#[cfg(feature = "api")]
use tokio::sync::{broadcast, RwLock};
#[cfg(feature = "api")]
use tiny_proxy::start_api_server;
use tiny_proxy::Proxy;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::INFO.into()))
.init();
let cli = Cli::parse();
info!("Tiny Proxy Server v{}", env!("CARGO_PKG_VERSION"));
info!("Loading config from: {}", cli.config);
let config = Config::from_file(&cli.config)?;
#[cfg(feature = "api")]
if cli.enable_api {
run_with_api(cli, config).await?;
} else {
run_proxy_only(cli, config).await?;
}
#[cfg(not(feature = "api"))]
run_proxy_only(cli, config).await?;
Ok(())
}
async fn run_proxy_only(cli: Cli, config: Config) -> Result<(), anyhow::Error> {
let mut proxy = Proxy::new(config);
if cli.max_concurrency > 0 {
proxy.set_max_concurrency(cli.max_concurrency);
}
info!("Starting proxy server on {}", cli.addr);
let shutdown_signal = setup_shutdown_signal();
tokio::select! {
result = proxy.start(&cli.addr) => {
if let Err(e) = result {
error!("Proxy server error: {}", e);
Err(e)
} else {
Ok(())
}
},
_ = shutdown_signal => {
info!("Shutdown signal received");
info!("Proxy server shutting down...");
Ok(())
}
}
}
#[cfg(feature = "api")]
async fn run_with_api(cli: Cli, config: Config) -> Result<(), anyhow::Error> {
let shared_config = Arc::new(RwLock::new(config.clone()));
let (shutdown_tx, _) = broadcast::channel::<()>(1);
info!("Starting proxy server on {}", cli.addr);
info!("Starting API server on {}", cli.api_addr);
let api_handle = tokio::spawn(run_api_server(
cli.api_addr.clone(),
shared_config.clone(),
shutdown_tx.subscribe(),
));
let proxy_handle = tokio::spawn(run_proxy_server(
cli.addr.clone(),
cli.max_concurrency,
shared_config,
shutdown_tx.subscribe(),
));
let mut api_handle = Some(api_handle);
let mut proxy_handle = Some(proxy_handle);
tokio::select! {
api_result = async { api_handle.as_mut().unwrap().await } => {
match api_result {
Ok(Ok(())) => info!("API server shut down gracefully"),
Ok(Err(e)) => error!("API server error: {}", e),
Err(e) => error!("API server task panicked: {}", e),
}
let _ = shutdown_tx.send(());
},
proxy_result = async { proxy_handle.as_mut().unwrap().await } => {
match proxy_result {
Ok(Ok(())) => info!("Proxy server shut down gracefully"),
Ok(Err(e)) => error!("Proxy server error: {}", e),
Err(e) => error!("Proxy server task panicked: {}", e),
}
let _ = shutdown_tx.send(());
},
_ = setup_shutdown_signal() => {
info!("Shutdown signal received");
let _ = shutdown_tx.send(());
}
}
info!("Waiting for servers to shut down...");
let timeout = tokio::time::Duration::from_secs(30);
match tokio::time::timeout(timeout, api_handle.take().unwrap()).await {
Ok(Ok(Ok(()))) => info!("API server shut down"),
Ok(Ok(Err(e))) => warn!("API server shutdown error: {}", e),
Ok(Err(e)) => warn!("API server task error: {}", e),
Err(_) => {
warn!("API server shutdown timeout");
if let Some(handle) = api_handle {
handle.abort();
}
}
}
match tokio::time::timeout(timeout, proxy_handle.take().unwrap()).await {
Ok(Ok(Ok(()))) => info!("Proxy server shut down"),
Ok(Ok(Err(e))) => warn!("Proxy server shutdown error: {}", e),
Ok(Err(e)) => warn!("Proxy server task error: {}", e),
Err(_) => {
warn!("Proxy server shutdown timeout");
if let Some(handle) = proxy_handle {
handle.abort();
}
}
}
info!("All servers shut down");
Ok(())
}
#[cfg(feature = "api")]
async fn run_proxy_server(
addr: String,
max_concurrency: usize,
shared_config: Arc<RwLock<Config>>,
mut shutdown_rx: broadcast::Receiver<()>,
) -> Result<(), anyhow::Error> {
let mut proxy = Proxy::from_shared(shared_config);
if max_concurrency > 0 {
proxy.set_max_concurrency(max_concurrency);
}
tokio::select! {
result = proxy.start(&addr) => {
result
},
_ = shutdown_rx.recv() => {
info!("Proxy server received shutdown signal");
Ok(())
}
}
}
#[cfg(feature = "api")]
async fn run_api_server(
addr: String,
shared_config: Arc<RwLock<Config>>,
mut shutdown_rx: broadcast::Receiver<()>,
) -> Result<(), anyhow::Error> {
tokio::select! {
result = start_api_server(&addr, shared_config) => {
result.map_err(|e| e.into())
},
_ = shutdown_rx.recv() => {
info!("API server received shutdown signal");
Ok(())
}
}
}
async fn setup_shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
let mut sigterm = match signal(SignalKind::terminate()) {
Ok(s) => s,
Err(e) => {
warn!("Failed to setup SIGTERM handler: {}", e);
return std::future::pending().await;
}
};
let mut sigint = match signal(SignalKind::interrupt()) {
Ok(s) => s,
Err(e) => {
warn!("Failed to setup SIGINT handler: {}", e);
return std::future::pending().await;
}
};
tokio::select! {
_ = sigterm.recv() => info!("SIGTERM received"),
_ = sigint.recv() => info!("SIGINT (Ctrl+C) received"),
}
}
#[cfg(windows)]
{
use tokio::signal::ctrl_c;
match ctrl_c().await {
Ok(()) => info!("Ctrl+C received"),
Err(e) => {
warn!("Failed to setup Ctrl+C handler: {}", e);
std::future::pending().await
}
}
}
#[cfg(not(any(unix, windows)))]
{
#[cfg(feature = "tokio/signal")]
{
use tokio::signal::ctrl_c;
match ctrl_c().await {
Ok(()) => info!("Ctrl+C received"),
Err(e) => {
warn!("Failed to setup Ctrl+C handler: {}", e);
std::future::pending().await
}
}
}
#[cfg(not(feature = "tokio/signal"))]
{
warn!("No signal support on this platform, manual shutdown required");
std::future::pending().await
}
}
}