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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
// SPDX-License-Identifier: MIT OR Apache-2.0
// This file is part of Static Web Server.
// See https://static-web-server.net/ for more information
// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
//! The server CLI options
use clap::Parser;
use hyper::StatusCode;
use std::path::PathBuf;
#[cfg(feature = "directory-listing")]
use crate::directory_listing::DirListFmt;
use crate::Result;
/// General server configuration available in CLI and config file options.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct General {
#[arg(long, short = 'a', default_value = "::", env = "SERVER_HOST")]
/// Host address (E.g 127.0.0.1 or ::1)
pub host: String,
#[arg(long, short = 'p', default_value = "80", env = "SERVER_PORT")]
/// Host port
pub port: u16,
#[cfg_attr(
feature = "http2",
arg(
long,
short = 'f',
env = "SERVER_LISTEN_FD",
conflicts_with_all(&["host", "port", "https_redirect"])
)
)]
#[cfg_attr(
not(feature = "http2"),
arg(
long,
short = 'f',
env = "SERVER_LISTEN_FD",
conflicts_with_all(&["host", "port"])
)
)]
/// Instead of binding to a TCP port, accept incoming connections to an already-bound TCP
/// socket listener on the specified file descriptor number (usually zero). Requires that the
/// parent process (e.g. inetd, launchd, or systemd) binds an address and port on behalf of
/// static-web-server, before arranging for the resulting file descriptor to be inherited by
/// static-web-server. Cannot be used in conjunction with the port and host arguments. The
/// included systemd unit file utilises this feature to increase security by allowing the
/// static-web-server to be sandboxed more completely.
pub fd: Option<usize>,
#[cfg_attr(
not(wasm),
arg(
long,
short = 'n',
default_value = "1",
env = "SERVER_THREADS_MULTIPLIER"
)
)]
#[cfg_attr(
wasm,
arg(
long,
short = 'n',
default_value = "2",
env = "SERVER_THREADS_MULTIPLIER"
)
)] // We use 2 as the threads multiplier in Wasm, 1 in Native
/// Number of worker threads multiplier that'll be multiplied by the number of system CPUs
/// using the formula: `worker threads = number of CPUs * n` where `n` is the value that changes here.
/// When multiplier value is 0 or 1 then one thread per core is used.
/// Number of worker threads result should be a number between 1 and 32,768 though it is advised to keep this value on the smaller side.
pub threads_multiplier: usize,
#[cfg_attr(
not(wasm),
arg(
long,
short = 'b',
default_value = "512",
env = "SERVER_MAX_BLOCKING_THREADS"
)
)]
#[cfg_attr(
wasm,
arg(
long,
short = 'b',
default_value = "20",
env = "SERVER_MAX_BLOCKING_THREADS"
)
)] // We use 20 in Wasm, 512 in Native (default for tokio)
/// Maximum number of blocking threads
pub max_blocking_threads: usize,
#[arg(long, short = 'd', default_value = "./public", env = "SERVER_ROOT")]
/// Root directory path of static files.
pub root: PathBuf,
#[arg(long, default_value = "./50x.html", env = "SERVER_ERROR_PAGE_50X")]
/// HTML file path for 50x errors. If the path is not specified or simply doesn't exist
/// then the server will use a generic HTML error message.
/// If a relative path is used then it will be resolved under the root directory.
pub page50x: PathBuf,
#[arg(long, default_value = "./404.html", env = "SERVER_ERROR_PAGE_404")]
/// HTML file path for 404 errors. If the path is not specified or simply doesn't exist
/// then the server will use a generic HTML error message.
/// If a relative path is used then it will be resolved under the root directory.
pub page404: PathBuf,
#[cfg(feature = "fallback-page")]
#[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
#[arg(long, default_value = "", value_parser = value_parser_pathbuf, env = "SERVER_FALLBACK_PAGE")]
/// HTML file path that is used for GET requests when the requested path doesn't exist. The fallback page is served with a 200 status code, useful when using client routers. If the path is not specified or simply doesn't exist then this feature will not be active.
pub page_fallback: PathBuf,
#[arg(long, short = 'g', default_value = "error", env = "SERVER_LOG_LEVEL")]
/// Specify a logging level in lower case. Values: error, warn, info, debug or trace
pub log_level: String,
#[arg(
long,
short = 'c',
default_value = "",
env = "SERVER_CORS_ALLOW_ORIGINS"
)]
/// Specify an optional CORS list of allowed origin hosts separated by commas. Host ports or protocols aren't being checked. Use an asterisk (*) to allow any host.
pub cors_allow_origins: String,
#[arg(
long,
short = 'j',
default_value = "origin, content-type",
env = "SERVER_CORS_ALLOW_HEADERS"
)]
/// Specify an optional CORS list of allowed headers separated by commas. Default "origin, content-type". It requires `--cors-allow-origins` to be used along with.
pub cors_allow_headers: String,
#[arg(
long,
default_value = "origin, content-type",
env = "SERVER_CORS_EXPOSE_HEADERS"
)]
/// Specify an optional CORS list of exposed headers separated by commas. Default "origin, content-type". It requires `--cors-expose-origins` to be used along with.
pub cors_expose_headers: String,
#[arg(
long,
short = 't',
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_HTTP2_TLS",
)]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
/// Enable HTTP/2 with TLS support.
pub http2: bool,
#[arg(long, required_if_eq("http2", "true"), env = "SERVER_HTTP2_TLS_CERT")]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
/// Specify the file path to read the certificate.
pub http2_tls_cert: Option<PathBuf>,
#[arg(long, required_if_eq("http2", "true"), env = "SERVER_HTTP2_TLS_KEY")]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
/// Specify the file path to read the private key.
pub http2_tls_key: Option<PathBuf>,
#[arg(
long,
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
requires_if("true", "http2"),
env = "SERVER_HTTPS_REDIRECT"
)]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
/// Redirect all requests with scheme "http" to "https" for the current server instance. It depends on "http2" to be enabled.
pub https_redirect: bool,
#[arg(
long,
requires_if("true", "https_redirect"),
default_value = "localhost",
env = "SERVER_HTTPS_REDIRECT_HOST"
)]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
/// Canonical host name or IP of the HTTPS (HTTPS/2) server. It depends on "https_redirect" to be enabled.
pub https_redirect_host: String,
#[arg(
long,
requires_if("true", "https_redirect"),
default_value = "80",
env = "SERVER_HTTPS_REDIRECT_FROM_PORT"
)]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
/// HTTP host port where the redirect server will listen for requests to redirect them to HTTPS. It depends on "https_redirect" to be enabled.
pub https_redirect_from_port: u16,
#[arg(
long,
requires_if("true", "https_redirect"),
default_value = "localhost",
env = "SERVER_HTTPS_REDIRECT_FROM_HOSTS"
)]
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
/// List of host names or IPs allowed to redirect from. HTTP requests must contain the HTTP 'Host' header and match against this list. It depends on "https_redirect" to be enabled.
pub https_redirect_from_hosts: String,
#[arg(long, default_value = "index.html", env = "SERVER_INDEX_FILES")]
/// List of files that will be used as an index for requests ending with the slash character (‘/’).
/// Files are checked in the specified order.
pub index_files: String,
#[cfg(feature = "compression")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
#[arg(
long,
short = 'x',
default_value = "true",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_COMPRESSION",
)]
/// Gzip, Deflate, Brotli or Zstd compression on demand determined by the Accept-Encoding header and applied to text-based web file types only.
pub compression: bool,
#[cfg(feature = "compression")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
#[arg(
long,
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_COMPRESSION_STATIC",
)]
/// Look up the pre-compressed file variant (`.gz`, `.br` or `.zst`) on disk of a requested file and serves it directly if available.
/// The compression type is determined by the `Accept-Encoding` header.
pub compression_static: bool,
#[cfg(feature = "directory-listing")]
#[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
#[arg(
long,
short = 'z',
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_DIRECTORY_LISTING",
)]
/// Enable directory listing for all requests ending with the slash character (‘/’).
pub directory_listing: bool,
#[cfg(feature = "directory-listing")]
#[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
#[arg(
long,
requires_if("true", "directory_listing"),
default_value = "6",
env = "SERVER_DIRECTORY_LISTING_ORDER"
)]
/// Specify a default code number to order directory listing entries per `Name`, `Last modified` or `Size` attributes (columns). Code numbers supported: 0 (Name asc), 1 (Name desc), 2 (Last modified asc), 3 (Last modified desc), 4 (Size asc), 5 (Size desc). Default 6 (unordered)
pub directory_listing_order: u8,
#[cfg(feature = "directory-listing")]
#[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
#[arg(
long,
value_enum,
requires_if("true", "directory_listing"),
default_value = "html",
env = "SERVER_DIRECTORY_LISTING_FORMAT",
ignore_case(true)
)]
/// Specify a content format for directory listing entries. Formats supported: "html" or "json". Default "html".
pub directory_listing_format: DirListFmt,
#[arg(
long,
default_value = "false",
default_value_if("http2", "true", Some("true")),
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_SECURITY_HEADERS",
)]
/// Enable security headers by default when HTTP/2 feature is activated.
/// Headers included: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload" (2 years max-age),
/// "X-Frame-Options: DENY" and "Content-Security-Policy: frame-ancestors 'self'".
pub security_headers: bool,
#[arg(
long,
short = 'e',
default_value = "true",
env = "SERVER_CACHE_CONTROL_HEADERS"
)]
#[arg(
long,
short = 'e',
default_value = "true",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_CACHE_CONTROL_HEADERS",
)]
/// Enable cache control headers for incoming requests based on a set of file types. The file type list can be found on `src/control_headers.rs` file.
pub cache_control_headers: bool,
#[cfg(feature = "basic-auth")]
/// It provides The "Basic" HTTP Authentication scheme using credentials as "user-id:password" pairs. Password must be encoded using the "BCrypt" password-hashing function.
#[arg(long, default_value = "", env = "SERVER_BASIC_AUTH")]
pub basic_auth: String,
#[arg(long, short = 'q', default_value = "0", env = "SERVER_GRACE_PERIOD")]
/// Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. The maximum value is 255 seconds.
pub grace_period: u8,
#[arg(
long,
short = 'w',
default_value = "./config.toml",
value_parser = value_parser_pathbuf,
env = "SERVER_CONFIG_FILE"
)]
/// Server TOML configuration file path.
pub config_file: PathBuf,
#[arg(
long,
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_LOG_REMOTE_ADDRESS",
)]
/// Log incoming requests information along with its remote address if available using the `info` log level.
pub log_remote_address: bool,
#[arg(
long,
default_value = "true",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_REDIRECT_TRAILING_SLASH",
)]
/// Check for a trailing slash in the requested directory URI and redirect permanently (308) to the same path with a trailing slash suffix if it is missing.
pub redirect_trailing_slash: bool,
#[arg(
long,
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_IGNORE_HIDDEN_FILES",
)]
/// Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing).
pub ignore_hidden_files: bool,
#[arg(
long,
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_HEALTH",
)]
/// Add a /health endpoint that doesn't generate any log entry and returns a 200 status code.
/// This is especially useful with Kubernetes liveness and readiness probes.
pub health: bool,
#[arg(
long,
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_MAINTENANCE_MODE"
)]
/// Enable the server's maintenance mode functionality.
pub maintenance_mode: bool,
#[arg(
long,
default_value = "503",
value_parser = value_parser_status_code,
requires_if("true", "maintenance_mode"),
env = "SERVER_MAINTENANCE_MODE_STATUS"
)]
/// Provide a custom HTTP status code when entering into maintenance mode. Default 503.
pub maintenance_mode_status: StatusCode,
#[arg(
long,
default_value = "",
value_parser = value_parser_pathbuf,
requires_if("true", "maintenance_mode"),
env = "SERVER_MAINTENANCE_MODE_FILE"
)]
/// Provide a custom maintenance mode HTML file. If not provided then a generic message will be displayed.
pub maintenance_mode_file: PathBuf,
//
// Windows specific arguments and commands
//
#[cfg(windows)]
#[arg(
long,
short = 's',
default_value = "false",
default_missing_value("true"),
num_args(0..=1),
require_equals(true),
action = clap::ArgAction::Set,
env = "SERVER_WINDOWS_SERVICE",
)]
/// Tell the web server to run in a Windows Service context. Note that the `install` subcommand will enable this option automatically.
pub windows_service: bool,
// Windows commands
#[cfg(windows)]
#[command(subcommand)]
/// Subcommands to install or uninstall the SWS Windows Service.
pub commands: Option<Commands>,
}
#[cfg(windows)]
#[derive(Debug, clap::Subcommand)]
/// Subcommands to install or uninstall the SWS Windows Service.
pub enum Commands {
/// Install a Windows Service for the web server.
#[command(name = "install")]
Install {},
/// Uninstall the current Windows Service.
#[command(name = "uninstall")]
Uninstall {},
}
fn value_parser_pathbuf(s: &str) -> Result<PathBuf, String> {
Ok(PathBuf::from(s))
}
fn value_parser_status_code(s: &str) -> Result<StatusCode, String> {
match s.parse::<u16>() {
Ok(code) => StatusCode::from_u16(code).map_err(|err| err.to_string()),
Err(err) => Err(err.to_string()),
}
}