use std::{ env, process::{exit, Command}, time::Duration, }; use axum::{ error_handling::HandleErrorLayer, extract::Path, http::StatusCode, response::Html, routing::get, BoxError, Router, }; use tower::{buffer::BufferLayer, limit::rate::RateLimitLayer, ServiceBuilder}; #[tokio::main] async fn main() { // initialize tracing tracing_subscriber::fmt::init(); // idk how to have one rate limiting block apply to both of these but this works so it's fine let app = Router::new() .route( "/:name", get(command).layer( ServiceBuilder::new() .layer(HandleErrorLayer::new(|err: BoxError| async move { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled error: {err}"), ) })) .layer(BufferLayer::new(1024)) .layer(RateLimitLayer::new(5, Duration::from_secs(1))), ), ) .route( "/", get(StatusCode::BAD_REQUEST).layer( ServiceBuilder::new() .layer(HandleErrorLayer::new(|err: BoxError| async move { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled error: {err}"), ) })) .layer(BufferLayer::new(1024)) .layer(RateLimitLayer::new(5, Duration::from_secs(1))), ), ); let args: Vec = env::args().collect(); if args.len() < 2 { println!("Provide a port with manserve $PORT"); exit(1); } if args[1].parse::().is_ok() { let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", args[1])) .await .unwrap(); println!("Starting server on port {}", args[1]); axum::serve(listener, app).await.unwrap(); } else { println!("Could not parse port {}", args[1]); exit(1); } } async fn command(Path(name): Path) -> Result, StatusCode> { let mut manpage_loc = String::from_utf8( Command::new("man") .args(["-w", name.as_str()]) .output() .unwrap() .stdout, ) .unwrap(); manpage_loc.pop(); let man = Command::new("mandoc") .arg("-Thtml") .arg("-O") .arg("fragment") .arg(manpage_loc) .output() .unwrap(); match man.status.success() { true => Ok(Html(String::from_utf8(man.stdout).unwrap())), false => Err(StatusCode::BAD_REQUEST), } }