use anyhow::Result;
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio::signal;
use tracing::{info, warn, error};
mod config;
mod tls;
mod server;
mod reload;
mod auth; mod rate_limiter; mod logger; mod destination_filter; mod ip_tracker; mod http_client; mod body_limiter; mod http_metrics; mod mixed_content;
use config::Config;
use reload::ReloadableTlsAcceptor;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive(tracing::Level::INFO.into())
)
.json()
.init();
info!("Starting Rust Forward Proxy Server...");
let config = Arc::new(Config::from_env()?);
info!("Configuration loaded");
rate_limiter::RateLimiter::start_cleanup_task(Arc::clone(&config.rate_limiter), 60);
info!("Rate limiter cleanup task started (interval: 60s)");
Arc::clone(&config.request_logger).start_background_flush();
info!(
"Request logger started (batch_size={}, interval={}s)",
config.log_batch_size, config.log_batch_interval_secs
);
let tls_acceptor = ReloadableTlsAcceptor::new(
config.cert_path.clone(),
config.key_path.clone(),
)?;
info!("TLS configured with ALPN protocols: h2, http/1.1");
info!("Certificate hot-reload enabled via SIGHUP");
let bind_addr = format!("{}:{}", config.host, config.port);
let listener = TcpListener::bind(&bind_addr).await?;
info!("Listening on {}", bind_addr);
let tls_acceptor_for_reload = tls_acceptor.clone();
let _reload_handle = tokio::spawn(async move {
reload_signal_handler(tls_acceptor_for_reload).await;
});
let mut shutdown = tokio::spawn(async {
shutdown_signal().await;
info!("Shutdown signal received");
});
loop {
tokio::select! {
result = listener.accept() => {
match result {
Ok((stream, peer_addr)) => {
info!("Accepted connection from {}", peer_addr);
let tls_acceptor = tls_acceptor.clone();
let config = Arc::clone(&config);
tokio::spawn(async move {
let acceptor = tls_acceptor.get().await;
match acceptor.accept(stream).await {
Ok(tls_stream) => {
let alpn_protocol = tls_stream
.get_ref()
.1
.alpn_protocol()
.map(|p| String::from_utf8_lossy(p).to_string());
info!("ALPN negotiated: {:?}", alpn_protocol);
match alpn_protocol.as_deref() {
Some("h2") => {
info!("HTTP/2 connection established");
if let Err(e) = server::serve_h2(tls_stream, config).await {
error!("HTTP/2 server error: {}", e);
}
}
Some("http/1.1") | None => {
info!("HTTP/1.1 connection established");
if let Err(e) = server::serve_http1(tls_stream, config).await {
error!("HTTP/1.1 server error: {}", e);
}
}
Some(proto) => {
warn!("Unsupported ALPN protocol: {}", proto);
}
}
}
Err(e) => {
error!("TLS handshake failed: {}", e);
}
}
});
}
Err(e) => {
error!("Failed to accept connection: {}", e);
}
}
}
_ = &mut shutdown => {
info!("Shutting down server...");
break;
}
}
}
Ok(())
}
async fn reload_signal_handler(tls_acceptor: ReloadableTlsAcceptor) {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
let mut sighup = signal(SignalKind::hangup())
.expect("Failed to install SIGHUP handler");
loop {
sighup.recv().await;
info!("SIGHUP received - initiating certificate reload");
if let Err(e) = tls_acceptor.reload().await {
error!("Certificate reload failed: {}", e);
}
}
}
#[cfg(not(unix))]
{
info!("Certificate hot-reload via SIGHUP not supported on Windows");
info!("Certificates can only be reloaded by restarting the server");
std::future::pending::<()>().await;
}
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("Failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {
info!("Received SIGINT (Ctrl+C)");
},
_ = terminate => {
info!("Received SIGTERM");
},
}
}