use actix_web::web::Payload; use actix_web::{App, Error, HttpRequest, HttpResponse, HttpServer, error, web}; use clap::Parser; use futures::StreamExt; const MAX_BODY_SIZE: usize = 262_144; // max payload size is 256k /// Simple HTTP responder #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { /// Listen address #[clap(short, long, env, default_value = "0.0.0.0:8000")] listen_address: String, } async fn handler(req: HttpRequest, mut payload: Payload) -> Result { let mut headers = req .headers() .iter() .map(|h| format!("{}: {}", h.0.as_str(), h.1.to_str().unwrap_or_default())) .collect::>(); headers.sort(); let mut body = web::BytesMut::new(); while let Some(chunk) = payload.next().await { let chunk = chunk?; // limit max size of in-memory payload if (body.len() + chunk.len()) > MAX_BODY_SIZE { return Err(error::ErrorBadRequest("overflow")); } body.extend_from_slice(&chunk); } let response = format!( "Remote Peer: {}\nVerb: {}\nPath: {}\nQuery: {}\n\n=== Headers ===\n{}\n\n=== Body ===\n{}", req.peer_addr().expect("Missing remote peer address!"), req.head().method, req.uri().path(), req.uri().query().unwrap_or_default(), headers.join("\n"), String::from_utf8_lossy(&body) ); Ok(HttpResponse::Ok().content_type("text/plain").body(response)) } #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let args: Args = Args::parse(); log::info!("Will listen on {}...", args.listen_address); HttpServer::new(|| App::new().route("{tail:.*}", web::route().to(handler))) .bind(args.listen_address)? .run() .await } #[cfg(test)] mod test { use crate::Args; #[test] fn verify_cli() { use clap::CommandFactory; Args::command().debug_assert() } }