mini-static 0.14.3

A secure, async static file server with streaming, traversal protection, and connection limits.
Documentation
//! Benchmarks `resolve_with_canonical_root` (and its convenience wrapper `resolve`)
//! across the request-path shapes it actually branches on: a shallow file, a deeply
//! nested file, a path requiring percent-decoding, a path rejected by the segment-based
//! traversal check, and a directory request that falls through to `index.html`.

use std::fs;
use std::path::Path;

use criterion::{Criterion, criterion_group, criterion_main};
use mini_static::resolve_with_canonical_root;
use tempfile::TempDir;

const DEEP_NESTING: usize = 10;

fn build_fixture_root() -> TempDir {
    let dir = tempfile::tempdir().expect("create fixture tempdir");
    let root = dir.path();

    fs::write(root.join("shallow.txt"), b"shallow").expect("write shallow fixture");

    let mut deep = root.to_path_buf();
    for i in 0..DEEP_NESTING {
        deep.push(format!("segment-{i}"));
    }
    fs::create_dir_all(&deep).expect("create deep fixture dirs");
    fs::write(deep.join("deep.txt"), b"deep").expect("write deep fixture");

    fs::write(root.join("caf\u{e9}.txt"), b"percent-decoded").expect("write encoded fixture");

    let index_dir = root.join("indexed");
    fs::create_dir_all(&index_dir).expect("create indexed dir");
    fs::write(index_dir.join("index.html"), b"<html></html>").expect("write index fixture");

    dir
}

fn deep_request_path() -> String {
    let mut path = String::new();
    for i in 0..DEEP_NESTING {
        path.push_str(&format!("/segment-{i}"));
    }
    path.push_str("/deep.txt");
    path
}

fn bench_path_resolution(c: &mut Criterion) {
    let fixture = build_fixture_root();
    let root_canon = fixture.path().canonicalize().expect("canonicalize fixture root");
    let deep_path = deep_request_path();

    let mut group = c.benchmark_group("resolve_with_canonical_root");

    group.bench_function("shallow", |b| {
        b.iter(|| resolve_with_canonical_root(&root_canon, "/shallow.txt"))
    });

    group.bench_function("deep_nested", |b| {
        b.iter(|| resolve_with_canonical_root(&root_canon, &deep_path))
    });

    group.bench_function("percent_decoded", |b| {
        b.iter(|| resolve_with_canonical_root(&root_canon, "/caf%C3%A9.txt"))
    });

    group.bench_function("traversal_rejected", |b| {
        b.iter(|| resolve_with_canonical_root(&root_canon, "/../../etc/passwd"))
    });

    group.bench_function("directory_index", |b| {
        b.iter(|| resolve_with_canonical_root(&root_canon, "/indexed"))
    });

    group.finish();

    // `resolve()` canonicalizes the root on every call — bench it separately so the
    // per-call root-canonicalization cost the docs warn about (vs. `Server`'s
    // canonicalize-once startup path) is visible on its own.
    c.bench_function("resolve_per_call_root_canonicalize", |b| {
        b.iter(|| mini_static::resolve(Path::new(fixture.path()), "/shallow.txt"))
    });
}

criterion_group!(benches, bench_path_resolution);
criterion_main!(benches);