use std::{net::IpAddr, path::PathBuf, time::Duration};
use anyhow::Result;
use clap::Parser;
use http_relay::HttpRelayBuilder;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
const DEFAULT_MAX_ENTRIES: usize = 10_000;
#[derive(Parser, Debug)]
#[command(name = "http-relay")]
#[command(about = "HTTP relay server for asynchronous producer/consumer communication")]
#[command(version)]
struct Args {
#[arg(short, long, default_value = "127.0.0.1")]
bind: IpAddr,
#[arg(short, long, default_value_t = 8080)]
port: u16,
#[arg(long, default_value_t = 600)]
link_timeout: u64,
#[arg(long, default_value_t = 25)]
inbox_timeout: u64,
#[arg(long, default_value_t = 300)]
inbox_cache_ttl: u64,
#[arg(long, default_value_t = 2 * 1024)]
max_body_size: usize,
#[arg(long, default_value_t = DEFAULT_MAX_ENTRIES)]
max_entries: usize,
#[arg(long)]
persist_db: Option<PathBuf>,
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
#[arg(long)]
cors_allow_all: bool,
#[arg(short, long)]
quiet: bool,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
init_tracing(args.verbose, args.quiet);
let relay = HttpRelayBuilder::default()
.bind_address(args.bind)
.http_port(args.port)
.link_timeout(Duration::from_secs(args.link_timeout))
.inbox_timeout(Duration::from_secs(args.inbox_timeout))
.inbox_cache_ttl(Duration::from_secs(args.inbox_cache_ttl))
.max_body_size(args.max_body_size)
.max_entries(args.max_entries)
.persist_db(args.persist_db)
.cors_allow_all(args.cors_allow_all)
.run()
.await?;
tracing::info!(
address = %relay.http_address(),
"HTTP relay server started"
);
#[cfg(feature = "link-compat")]
tracing::info!(
url = %relay.local_url(),
"Endpoints: /inbox/{{id}} (recommended), /link/{{id}}"
);
#[cfg(not(feature = "link-compat"))]
tracing::info!(
url = %relay.local_url(),
"Endpoint: /inbox/{{id}}"
);
tokio::signal::ctrl_c().await?;
tracing::info!("Shutting down...");
relay.shutdown().await?;
Ok(())
}
fn log_level(verbose: u8, quiet: bool) -> LevelFilter {
if quiet {
LevelFilter::OFF
} else {
match verbose {
0 => LevelFilter::WARN,
1 => LevelFilter::INFO,
2 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
}
}
}
fn init_tracing(verbose: u8, quiet: bool) {
let filter = EnvFilter::builder()
.with_default_directive(log_level(verbose, quiet).into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_target(false)
.init();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quiet_overrides_verbose() {
assert_eq!(log_level(3, true), LevelFilter::OFF);
}
#[test]
fn test_default_is_warn() {
assert_eq!(log_level(0, false), LevelFilter::WARN);
}
#[test]
fn test_verbose_info() {
assert_eq!(log_level(1, false), LevelFilter::INFO);
}
#[test]
fn test_verbose_debug() {
assert_eq!(log_level(2, false), LevelFilter::DEBUG);
}
#[test]
fn test_verbose_trace() {
assert_eq!(log_level(3, false), LevelFilter::TRACE);
}
}