mini-static 0.20.0

A secure, async static file server with streaming, traversal protection, and connection limits.
Documentation
use super::*;

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;

/// Fails `accept()` a fixed number of times, recording the (paused, virtual)
/// instant of each attempt, before delegating to a real listener so the caller can
/// eventually succeed.
struct FlakyListener {
    inner: TcpListener,
    remaining_failures: AtomicUsize,
    attempts: Mutex<Vec<tokio::time::Instant>>,
}

impl TcpAccept for FlakyListener {
    async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)> {
        self.attempts
            .lock()
            .unwrap()
            .push(tokio::time::Instant::now());
        if self.remaining_failures.fetch_sub(1, Ordering::SeqCst) > 0 {
            Err(std::io::Error::other("simulated accept error"))
        } else {
            TcpAccept::accept(&self.inner).await
        }
    }
}

// Disproves the prior implementation, which broke out of the accept loop entirely
// on the first `accept()` error — permanently ending the server. This test would
// also fail against a naive `continue`-only fix (no backoff): the recorded gaps
// between attempts would collapse to ~0 (a busy spin) instead of the expected
// exponentially growing delays.
#[tokio::test(start_paused = true)]
async fn accept_loop_backs_off_between_repeated_errors_instead_of_busy_spinning() {
    let inner = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
    let addr = inner.local_addr().unwrap();

    let flaky = FlakyListener {
        inner,
        remaining_failures: AtomicUsize::new(5),
        attempts: Mutex::new(Vec::new()),
    };

    tokio::spawn(async move {
        let _ = TcpStream::connect(addr).await;
    });

    let semaphore = Arc::new(Semaphore::new(1));
    let mut backoff = ACCEPT_BACKOFF_INITIAL;
    let result = accept_and_permit(&flaky, &mut backoff, &semaphore).await;
    assert!(
        result.is_some(),
        "accept should eventually succeed once the flaky listener stops failing"
    );

    let recorded = flaky.attempts.lock().unwrap();
    assert_eq!(recorded.len(), 6, "5 failures then 1 success");

    let expected_gaps = [
        ACCEPT_BACKOFF_INITIAL,
        ACCEPT_BACKOFF_INITIAL * 2,
        ACCEPT_BACKOFF_INITIAL * 4,
        ACCEPT_BACKOFF_INITIAL * 8,
        ACCEPT_BACKOFF_INITIAL * 16,
    ];
    for (i, expected) in expected_gaps.iter().enumerate() {
        let gap = recorded[i + 1] - recorded[i];
        assert_eq!(
            gap,
            *expected,
            "gap between attempt {i} and {} should reflect the backoff delay, not a busy spin",
            i + 1
        );
    }

    // The delay must stop doubling at the cap rather than growing without bound.
    let mut capped = ACCEPT_BACKOFF_MAX;
    capped = (capped * 2).min(ACCEPT_BACKOFF_MAX);
    assert_eq!(capped, ACCEPT_BACKOFF_MAX);
}

// A successful accept must clear the accumulated delay, so an isolated error later
// on doesn't inherit a second-long wait from an unrelated earlier failure.
#[tokio::test(start_paused = true)]
async fn a_successful_accept_resets_the_backoff() {
    let inner = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
    let addr = inner.local_addr().unwrap();
    let flaky = FlakyListener {
        inner,
        remaining_failures: AtomicUsize::new(3),
        attempts: Mutex::new(Vec::new()),
    };
    tokio::spawn(async move {
        let _ = TcpStream::connect(addr).await;
    });

    let semaphore = Arc::new(Semaphore::new(1));
    let mut backoff = ACCEPT_BACKOFF_INITIAL * 32;
    accept_and_permit(&flaky, &mut backoff, &semaphore).await;

    assert_eq!(
        backoff, ACCEPT_BACKOFF_INITIAL,
        "the delay must return to its initial value once an accept succeeds"
    );
}