recon-cli 0.92.2

Versatile network reconnaissance CLI: HTTP/TLS/DNS, multi-protocol probes, and a Rhai script engine
Documentation
use anyhow::{Context, Result};
use bytes::Bytes;
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use std::convert::Infallible;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use tokio::net::TcpListener;

use super::{files, log_request, LogFile};

pub async fn serve(port: u16, root: Arc<PathBuf>, log_file: LogFile) -> Result<()> {
    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    let listener = TcpListener::bind(addr)
        .await
        .with_context(|| format!("Failed to bind HTTP listener on {addr}"))?;

    loop {
        let (stream, remote_addr) = listener.accept().await?;
        let root = Arc::clone(&root);
        let log_file = log_file.clone();

        tokio::spawn(async move {
            let root = root;
            let log_file = log_file;

            let service = service_fn(move |req: Request<Incoming>| {
                let root = Arc::clone(&root);
                let log_file = log_file.clone();
                async move {
                    let start = Instant::now();
                    let method = req.method().to_string();
                    let path = req.uri().path().to_string();

                    let served = files::handle_request(&req, &root);
                    let status = served.response.status().as_u16();
                    let bytes = served.bytes;
                    let elapsed = start.elapsed();

                    log_request(remote_addr, &method, &path, status, bytes, elapsed, &log_file);

                    Ok::<Response<Full<Bytes>>, Infallible>(served.response)
                }
            });

            let result = hyper::server::conn::http1::Builder::new()
                .serve_connection(TokioIo::new(stream), service)
                .await;

            if let Err(e) = result {
                let msg = e.to_string();
                if !msg.contains("connection reset") && !msg.contains("Connection reset") {
                    eprintln!("HTTP connection error: {e}");
                }
            }
        });
    }
}