1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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);
}
}