trillium-cli 0.6.0

The trillium.rs cli
Documentation
use crate::{
    ratelimit::RateLimit,
    server_tls::ServerTls,
    tls::{Tls, parse_url},
};
use clap::Parser;
use clap_verbosity_flag::Verbosity;
use colored::Colorize;
use std::{fmt::Debug, io::Write};
use trillium_logger::Logger;
use trillium_proxy::{Client, Proxy, Url};
use trillium_static::StaticFileHandler;

mod root_path;
use crate::directory_listing::DirectoryListing;
use root_path::RootPath;

#[derive(Parser, Debug)]
pub struct StaticCli {
    /// Filesystem path to serve
    ///
    /// Defaults to the current working directory
    #[arg(default_value_t)]
    root: RootPath,

    /// Local host or ip to listen on
    #[arg(short = 'o', long, env, default_value = "localhost")]
    host: String,

    /// Local port to listen on
    #[arg(short, long, env, default_value = "8080")]
    port: u16,

    #[command(flatten)]
    server_tls: ServerTls,

    /// Host to forward (reverse proxy) not-found requests to
    ///
    /// This forwards any request that would otherwise be a 404 Not
    /// Found to the specified listener spec.
    ///
    /// Examples:
    ///    `--forward localhost:8081`
    ///    `--forward http://localhost:8081`
    ///    `--forward https://localhost:8081`
    ///
    /// Note: http+unix:// schemes are not yet supported
    #[arg(short, long, env = "FORWARD", value_parser = parse_url)]
    forward: Option<Url>,

    #[arg(short, long, env)]
    index: Option<String>,

    /// disable response compression (gzip/brotli/zstd)
    #[arg(long)]
    no_compress: bool,

    /// serve an HTML directory listing for directories without an index file
    ///
    /// When enabled, a request that resolves to a directory with no index file
    /// renders a listing of that directory's contents instead of returning 404
    /// Not Found. Off by default, since it exposes file names and structure.
    #[arg(short = 'l', long, env)]
    directory_listing: bool,

    #[command(flatten)]
    rate_limit: RateLimit,

    #[command(flatten)]
    verbose: Verbosity,
}

impl StaticCli {
    pub fn run(self) {
        env_logger::Builder::new()
            .parse_filters(&format!(
                "{},quinn=off,quinn_proto=off",
                self.verbose.log_level_filter()
            ))
            .format(|buf, record| {
                writeln!(
                    buf,
                    "[{}] {}",
                    record.module_path().unwrap_or_default().dimmed(),
                    record.args()
                )
            })
            .init();

        let path = self.root.clone();
        let mut static_file_handler = StaticFileHandler::new(path);
        if let Some(index) = &self.index {
            static_file_handler = static_file_handler.with_index_file(index);
        }

        let server = (
            Logger::new(),
            self.rate_limit.limiter(),
            // `Option<Handler>` is a `Handler`, so `None` skips compression entirely.
            (!self.no_compress).then(trillium_compression::compression),
            self.forward
                .clone()
                .map(|url| Proxy::new(Client::from(Tls::default()), url)),
            static_file_handler,
            // Runs only when the file handler resolved a directory it had no
            // index for; otherwise leaves the conn untouched for the 404 path.
            self.directory_listing.then_some(DirectoryListing),
        );

        let config = trillium_smol::config()
            .with_port(self.port)
            .with_host(&self.host);

        self.server_tls.run_with_tls(config, server);
    }
}