refactor: general and advanced settings
Diff
src/bin/server.rs | 2 +-
src/config.rs | 182 +--------------------------------------------------
src/lib.rs | 5 +-
src/manifest.rs | 144 +----------------------------------------
src/server.rs | 112 ++++++++++++-------------------
src/settings/cli.rs | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++-
src/settings/file.rs | 173 ++++++++++++++++++++++++++++++++++++++++++++++++-
src/settings/mod.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++-
tests/toml/config.toml | 72 ++++++++++----------
9 files changed, 602 insertions(+), 430 deletions(-)
@@ -10,7 +10,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
use static_web_server::{Result, Server};
fn main() -> Result {
Server::new().run()?;
Server::new()?.run()?;
Ok(())
}
@@ -1,182 +0,0 @@
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(about, author)]
pub struct Config {
#[structopt(long, short = "a", default_value = "::", env = "SERVER_HOST")]
pub host: String,
#[structopt(long, short = "p", default_value = "80", env = "SERVER_PORT")]
pub port: u16,
#[structopt(
long,
short = "f",
env = "SERVER_LISTEN_FD",
conflicts_with_all(&["host", "port"])
)]
pub fd: Option<usize>,
#[structopt(
long,
short = "n",
default_value = "1",
env = "SERVER_THREADS_MULTIPLIER"
)]
pub threads_multiplier: usize,
#[structopt(long, short = "d", default_value = "./public", env = "SERVER_ROOT")]
pub root: String,
#[structopt(
long,
default_value = "./public/50x.html",
env = "SERVER_ERROR_PAGE_50X"
)]
pub page50x: String,
#[structopt(
long,
default_value = "./public/404.html",
env = "SERVER_ERROR_PAGE_404"
)]
pub page404: String,
#[structopt(long, default_value = "", env = "SERVER_FALLBACK_PAGE")]
pub page_fallback: String,
#[structopt(long, short = "g", default_value = "error", env = "SERVER_LOG_LEVEL")]
pub log_level: String,
#[structopt(
long,
short = "c",
default_value = "",
env = "SERVER_CORS_ALLOW_ORIGINS"
)]
pub cors_allow_origins: String,
#[structopt(
long,
short = "j",
default_value = "origin, content-type",
env = "SERVER_CORS_ALLOW_HEADERS"
)]
pub cors_allow_headers: String,
#[structopt(
long,
short = "t",
parse(try_from_str),
default_value = "false",
env = "SERVER_HTTP2_TLS"
)]
pub http2: bool,
#[structopt(
long,
required_if("http2", "true"),
default_value = "",
env = "SERVER_HTTP2_TLS_CERT"
)]
pub http2_tls_cert: String,
#[structopt(
long,
required_if("http2", "true"),
default_value = "",
env = "SERVER_HTTP2_TLS_KEY"
)]
pub http2_tls_key: String,
#[structopt(
long,
short = "x",
parse(try_from_str),
default_value = "true",
env = "SERVER_COMPRESSION"
)]
pub compression: bool,
#[structopt(
long,
short = "z",
parse(try_from_str),
default_value = "false",
env = "SERVER_DIRECTORY_LISTING"
)]
pub directory_listing: bool,
#[structopt(
long,
required_if("directory_listing", "true"),
default_value = "6",
env = "SERVER_DIRECTORY_LISTING_ORDER"
)]
pub directory_listing_order: u8,
#[structopt(
long,
parse(try_from_str),
required_if("http2", "true"),
default_value_if("http2", Some("true"), "true"),
default_value = "false",
env = "SERVER_SECURITY_HEADERS"
)]
pub security_headers: bool,
#[structopt(
long,
short = "e",
parse(try_from_str),
default_value = "true",
env = "SERVER_CACHE_CONTROL_HEADERS"
)]
pub cache_control_headers: bool,
#[structopt(long, default_value = "", env = "SERVER_BASIC_AUTH")]
pub basic_auth: String,
#[structopt(long, short = "q", default_value = "0", env = "SERVER_GRACE_PERIOD")]
pub grace_period: u8,
#[structopt(long, short = "w", env = "SERVER_CONFIG_FILE")]
pub config_file: Option<PathBuf>,
}
@@ -11,7 +11,6 @@ extern crate serde;
pub mod basic_auth;
pub mod compression;
pub mod config;
pub mod control_headers;
pub mod cors;
pub mod error_page;
@@ -19,10 +18,10 @@ pub mod fallback_page;
pub mod handler;
pub mod helpers;
pub mod logger;
pub mod manifest;
pub mod security_headers;
pub mod server;
pub mod service;
pub mod settings;
pub mod signals;
pub mod static_files;
pub mod tls;
@@ -31,6 +30,6 @@ pub mod transport;
#[macro_use]
pub mod error;
pub use config::Config;
pub use error::*;
pub use server::Server;
pub use settings::Settings;
@@ -1,144 +0,0 @@
use headers::HeaderMap;
use serde::Deserialize;
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use crate::{helpers, Context, Result};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Headers {
pub source: String,
#[serde(with = "http_serde::header_map")]
pub headers: HeaderMap,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Manifest {
pub host: Option<String>,
pub port: Option<u8>,
pub root: Option<String>,
pub log_level: Option<LogLevel>,
pub cache_control_headers: bool,
pub compression: bool,
pub page404: Option<String>,
pub page50x: Option<String>,
pub http2: bool,
pub http2_tls_cert: PathBuf,
pub http2_tls_key: PathBuf,
pub security_headers: bool,
pub cors_allow_origins: String,
pub cors_allow_headers: Option<String>,
pub directory_listing: bool,
pub directory_listing_order: Option<u8>,
pub basic_auth: Option<String>,
pub fd: Option<usize>,
pub threads_multiplier: usize,
pub grace_period: u8,
pub page_fallback: String,
#[serde(rename(deserialize = "headers"))]
pub headers: Option<Vec<Headers>>,
}
fn read_file(path: &Path) -> Result<toml::Value> {
let toml_str = helpers::read_file(path).with_context(|| {
format!(
"error trying to deserialize configuration \"{}\" file toml.",
path.display()
)
})?;
let first_error = match toml_str.parse() {
Ok(res) => return Ok(res),
Err(err) => err,
};
let mut second_parser = toml::de::Deserializer::new(&toml_str);
second_parser.set_require_newline_after_table(false);
if let Ok(res) = toml::Value::deserialize(&mut second_parser) {
let msg = format!(
"\
TOML file found which contains invalid syntax and will soon not parse
at `{}`.
The TOML spec requires newlines after table definitions (e.g., `[a] b = 1` is
invalid), but this file has a table header which does not have a newline after
it. A newline needs to be added and this warning will soon become a hard error
in the future.",
path.display()
);
println!("{}", &msg);
return Ok(res);
}
let first_error = anyhow::Error::from(first_error);
Err(first_error.context("could not parse input as TOML format"))
}
pub fn read_manifest(config_file: &Path) -> Result<Option<Manifest>> {
let ext = config_file.extension();
if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") {
return Ok(None);
}
let toml = read_file(config_file).with_context(|| "error reading configuration toml file.")?;
let mut unused = BTreeSet::new();
let manifest: Manifest = serde_ignored::deserialize(toml, |path| {
let mut key = String::new();
helpers::stringify(&mut key, &path);
unused.insert(key);
})
.with_context(|| "error during configuration toml file deserialization.")?;
for key in unused {
println!(
"Warning: unused configuration manifest key \"{}\" or unsuported.",
key
);
}
Ok(Some(manifest))
}
@@ -3,33 +3,32 @@ use hyper::server::Server as HyperServer;
use listenfd::ListenFd;
use std::net::{IpAddr, SocketAddr, TcpListener};
use std::sync::Arc;
use structopt::StructOpt;
use crate::handler::{RequestHandler, RequestHandlerOpts};
use crate::tls::{TlsAcceptor, TlsConfigBuilder};
use crate::{config::Config, service::RouterService, Context, Result};
use crate::{cors, helpers, logger, manifest, signals};
use crate::{cors, helpers, logger, signals, Settings};
use crate::{service::RouterService, Context, Result};
pub struct Server {
opts: Config,
opts: Settings,
threads: usize,
}
impl Server {
pub fn new() -> Server {
pub fn new() -> Result<Server> {
let opts = Config::from_args();
let opts = Settings::get()?;
let cpus = num_cpus::get();
let threads = match opts.threads_multiplier {
let threads = match opts.general.threads_multiplier {
0 | 1 => cpus,
n => cpus * n,
};
Server { opts, threads }
Ok(Server { opts, threads })
}
@@ -53,52 +52,40 @@ impl Server {
async fn start_server(self) -> Result {
let opts = &self.opts;
logger::init(&opts.log_level)
.with_context(|| "failed to initialize logging".to_string())?;
if let Some(config_file) = &opts.config_file {
if config_file.is_file() {
let path_resolved = config_file
.canonicalize()
.with_context(|| "error resolving config file path.")?;
let manifest = manifest::read_manifest(&path_resolved).with_context(|| {
format!(
"can not get \"{}\" config file because has invalid format or inaccessible",
path_resolved.display()
)
})?;
let general = self.opts.general;
println!("{:?}", manifest.unwrap().headers);
}
let log_level = &general.log_level.to_lowercase();
logger::init(log_level).with_context(|| "failed to initialize logging")?;
tracing::info!("logging level: {}", log_level.to_lowercase());
if general.config_file.is_some() && general.config_file.is_some() {
tracing::info!("config file: {}", general.config_file.unwrap().display());
}
let (tcp_listener, addr_str);
match opts.fd {
match general.fd {
Some(fd) => {
addr_str = format!("@FD({})", fd);
tcp_listener = ListenFd::from_env()
.take_tcp_listener(fd)?
.with_context(|| {
"failed to convert inherited FD into a TCP listener".to_string()
})?;
.with_context(|| "failed to convert inherited FD into a TCP listener")?;
tracing::info!(
"converted inherited file descriptor {} to a TCP listener",
fd
);
}
None => {
let ip = opts
let ip = general
.host
.parse::<IpAddr>()
.with_context(|| format!("failed to parse {} address", opts.host))?;
let addr = SocketAddr::from((ip, opts.port));
.with_context(|| format!("failed to parse {} address", general.host))?;
let addr = SocketAddr::from((ip, general.port));
tcp_listener = TcpListener::bind(addr)
.with_context(|| format!("failed to bind to {} address", addr))?;
addr_str = addr.to_string();
@@ -107,55 +94,55 @@ impl Server {
}
let root_dir = helpers::get_valid_dirpath(&opts.root)
.with_context(|| "root directory was not found or inaccessible".to_string())?;
let root_dir = helpers::get_valid_dirpath(&general.root)
.with_context(|| "root directory was not found or inaccessible")?;
let page404 = helpers::read_file_content(&opts.page404);
let page50x = helpers::read_file_content(&opts.page50x);
let page404 = helpers::read_file_content(&general.page404);
let page50x = helpers::read_file_content(&general.page50x);
let page_fallback = helpers::read_file_content(&opts.page_fallback);
let page_fallback = helpers::read_file_content(&general.page_fallback);
let threads = self.threads;
tracing::info!("runtime worker threads: {}", self.threads);
let security_headers = opts.security_headers;
let security_headers = general.security_headers;
tracing::info!("security headers: enabled={}", security_headers);
let compression = opts.compression;
let compression = general.compression;
tracing::info!("auto compression: enabled={}", compression);
let dir_listing = opts.directory_listing;
let dir_listing = general.directory_listing;
tracing::info!("directory listing: enabled={}", dir_listing);
let dir_listing_order = opts.directory_listing_order;
let dir_listing_order = general.directory_listing_order;
tracing::info!("directory listing order code: {}", dir_listing_order);
let cache_control_headers = opts.cache_control_headers;
let cache_control_headers = general.cache_control_headers;
tracing::info!("cache control headers: enabled={}", cache_control_headers);
let cors = cors::new(
opts.cors_allow_origins.trim(),
opts.cors_allow_headers.trim(),
general.cors_allow_origins.trim(),
general.cors_allow_headers.trim(),
);
let basic_auth = opts.basic_auth.trim().to_owned();
let basic_auth = general.basic_auth.trim().to_owned();
tracing::info!(
"basic authentication: enabled={}",
!self.opts.basic_auth.is_empty()
!general.basic_auth.is_empty()
);
let grace_period = opts.grace_period;
let grace_period = general.grace_period;
tracing::info!("grace period before graceful shutdown: {}s", grace_period);
@@ -177,31 +164,30 @@ impl Server {
if opts.http2 {
if general.http2 {
tcp_listener
.set_nonblocking(true)
.expect("cannot set non-blocking");
let listener = tokio::net::TcpListener::from_std(tcp_listener)
.with_context(|| "failed to create tokio::net::TcpListener".to_string())?;
.with_context(|| "failed to create tokio::net::TcpListener")?;
let mut incoming = AddrIncoming::from_listener(listener).with_context(|| {
"failed to create an AddrIncoming from the current tokio::net::TcpListener"
.to_string()
})?;
incoming.set_nodelay(true);
let tls = TlsConfigBuilder::new()
.cert_path(&opts.http2_tls_cert)
.key_path(&opts.http2_tls_key)
.cert_path(&general.http2_tls_cert)
.key_path(&general.http2_tls_key)
.build()
.with_context(|| {
"failed to initialize TLS, probably wrong cert/key or file missing".to_string()
"failed to initialize TLS, probably wrong cert/key or file missing"
})?;
#[cfg(unix)]
let signals = signals::create_signals()
.with_context(|| "failed to register termination signals".to_string())?;
.with_context(|| "failed to register termination signals")?;
#[cfg(unix)]
let handle = signals.handle();
@@ -231,7 +217,7 @@ impl Server {
#[cfg(unix)]
let signals = signals::create_signals()
.with_context(|| "failed to register termination signals".to_string())?;
.with_context(|| "failed to register termination signals")?;
#[cfg(unix)]
let handle = signals.handle();
@@ -265,9 +251,3 @@ impl Server {
Ok(())
}
}
impl Default for Server {
fn default() -> Self {
Self::new()
}
}
@@ -0,0 +1,182 @@
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(about, author)]
pub struct General {
#[structopt(long, short = "a", default_value = "::", env = "SERVER_HOST")]
pub host: String,
#[structopt(long, short = "p", default_value = "80", env = "SERVER_PORT")]
pub port: u16,
#[structopt(
long,
short = "f",
env = "SERVER_LISTEN_FD",
conflicts_with_all(&["host", "port"])
)]
pub fd: Option<usize>,
#[structopt(
long,
short = "n",
default_value = "1",
env = "SERVER_THREADS_MULTIPLIER"
)]
pub threads_multiplier: usize,
#[structopt(long, short = "d", default_value = "./public", env = "SERVER_ROOT")]
pub root: String,
#[structopt(
long,
default_value = "./public/50x.html",
env = "SERVER_ERROR_PAGE_50X"
)]
pub page50x: String,
#[structopt(
long,
default_value = "./public/404.html",
env = "SERVER_ERROR_PAGE_404"
)]
pub page404: String,
#[structopt(long, default_value = "", env = "SERVER_FALLBACK_PAGE")]
pub page_fallback: String,
#[structopt(long, short = "g", default_value = "error", env = "SERVER_LOG_LEVEL")]
pub log_level: String,
#[structopt(
long,
short = "c",
default_value = "",
env = "SERVER_CORS_ALLOW_ORIGINS"
)]
pub cors_allow_origins: String,
#[structopt(
long,
short = "j",
default_value = "origin, content-type",
env = "SERVER_CORS_ALLOW_HEADERS"
)]
pub cors_allow_headers: String,
#[structopt(
long,
short = "t",
parse(try_from_str),
default_value = "false",
env = "SERVER_HTTP2_TLS"
)]
pub http2: bool,
#[structopt(
long,
required_if("http2", "true"),
default_value = "",
env = "SERVER_HTTP2_TLS_CERT"
)]
pub http2_tls_cert: String,
#[structopt(
long,
required_if("http2", "true"),
default_value = "",
env = "SERVER_HTTP2_TLS_KEY"
)]
pub http2_tls_key: String,
#[structopt(
long,
short = "x",
parse(try_from_str),
default_value = "true",
env = "SERVER_COMPRESSION"
)]
pub compression: bool,
#[structopt(
long,
short = "z",
parse(try_from_str),
default_value = "false",
env = "SERVER_DIRECTORY_LISTING"
)]
pub directory_listing: bool,
#[structopt(
long,
required_if("directory_listing", "true"),
default_value = "6",
env = "SERVER_DIRECTORY_LISTING_ORDER"
)]
pub directory_listing_order: u8,
#[structopt(
long,
parse(try_from_str),
required_if("http2", "true"),
default_value_if("http2", Some("true"), "true"),
default_value = "false",
env = "SERVER_SECURITY_HEADERS"
)]
pub security_headers: bool,
#[structopt(
long,
short = "e",
parse(try_from_str),
default_value = "true",
env = "SERVER_CACHE_CONTROL_HEADERS"
)]
pub cache_control_headers: bool,
#[structopt(long, default_value = "", env = "SERVER_BASIC_AUTH")]
pub basic_auth: String,
#[structopt(long, short = "q", default_value = "0", env = "SERVER_GRACE_PERIOD")]
pub grace_period: u8,
#[structopt(long, short = "w", env = "SERVER_CONFIG_FILE")]
pub config_file: Option<PathBuf>,
}
@@ -0,0 +1,173 @@
use headers::HeaderMap;
use serde::Deserialize;
use std::collections::BTreeSet;
use std::path::Path;
use crate::{helpers, Context, Result};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl LogLevel {
pub fn name(&self) -> &'static str {
match self {
LogLevel::Error => "error",
LogLevel::Warn => "warn",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
LogLevel::Trace => "trace",
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Headers {
pub source: String,
#[serde(with = "http_serde::header_map")]
pub headers: HeaderMap,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Advanced {
#[serde(rename(deserialize = "headers"))]
pub headers: Option<Vec<Headers>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct General {
pub host: Option<String>,
pub port: Option<u16>,
pub root: Option<String>,
pub log_level: Option<LogLevel>,
pub cache_control_headers: Option<bool>,
pub compression: Option<bool>,
pub page404: Option<String>,
pub page50x: Option<String>,
pub http2: Option<bool>,
pub http2_tls_cert: Option<String>,
pub http2_tls_key: Option<String>,
pub security_headers: Option<bool>,
pub cors_allow_origins: Option<String>,
pub cors_allow_headers: Option<String>,
pub directory_listing: Option<bool>,
pub directory_listing_order: Option<u8>,
pub basic_auth: Option<String>,
pub fd: Option<usize>,
pub threads_multiplier: Option<usize>,
pub grace_period: Option<u8>,
pub page_fallback: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Settings {
pub general: General,
pub advanced: Advanced,
}
impl Settings {
pub fn read(config_file: &Path) -> Result<Settings> {
let ext = config_file.extension();
if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") {
bail!("configuration file should be in toml format. E.g `config.toml`");
}
let toml =
read_toml_file(config_file).with_context(|| "error reading toml configuration file")?;
let mut unused = BTreeSet::new();
let manifest: Settings = serde_ignored::deserialize(toml, |path| {
let mut key = String::new();
helpers::stringify(&mut key, &path);
unused.insert(key);
})
.with_context(|| "error during toml configuration file deserialization")?;
for key in unused {
println!(
"Warning: unused configuration manifest key \"{}\" or unsuported",
key
);
}
Ok(manifest)
}
}
fn read_toml_file(path: &Path) -> Result<toml::Value> {
let toml_str = helpers::read_file(path).with_context(|| {
format!(
"error trying to deserialize toml configuration file at \"{}\"",
path.display()
)
})?;
let first_error = match toml_str.parse() {
Ok(res) => return Ok(res),
Err(err) => err,
};
let mut second_parser = toml::de::Deserializer::new(&toml_str);
second_parser.set_require_newline_after_table(false);
if let Ok(res) = toml::Value::deserialize(&mut second_parser) {
let msg = format!(
"\
TOML file found which contains invalid syntax and will soon not parse
at `{}`.
The TOML spec requires newlines after table definitions (e.g., `[a] b = 1` is
invalid), but this file has a table header which does not have a newline after
it. A newline needs to be added and this warning will soon become a hard error
in the future.",
path.display()
);
println!("{}", &msg);
return Ok(res);
}
let first_error = anyhow::Error::from(first_error);
Err(first_error.context("could not parse data input as toml format"))
}
@@ -0,0 +1,160 @@
use structopt::StructOpt;
use crate::{Context, Result};
mod cli;
mod file;
use cli::General;
use file::Advanced;
pub struct Settings {
pub general: General,
pub advanced: Option<Advanced>,
}
impl Settings {
pub fn get() -> Result<Settings> {
let opts = General::from_args();
let mut host = opts.host.to_owned();
let mut port = opts.port;
let mut root = opts.root.to_owned();
let mut log_level = opts.log_level.to_owned();
let mut config_file = opts.config_file.clone();
let mut cache_control_headers = opts.cache_control_headers;
let mut compression = opts.compression;
let mut page404 = opts.page404.to_owned();
let mut page50x = opts.page50x.to_owned();
let mut http2 = opts.http2;
let mut http2_tls_cert = opts.http2_tls_cert.to_owned();
let mut http2_tls_key = opts.http2_tls_key.to_owned();
let mut security_headers = opts.security_headers;
let mut cors_allow_origins = opts.cors_allow_origins.to_owned();
let mut cors_allow_headers = opts.cors_allow_headers.to_owned();
let mut directory_listing = opts.directory_listing;
let mut directory_listing_order = opts.directory_listing_order;
let mut basic_auth = opts.basic_auth.to_owned();
let mut fd = opts.fd;
let mut threads_multiplier = opts.threads_multiplier;
let mut grace_period = opts.grace_period;
let mut page_fallback = opts.page_fallback.to_owned();
let mut settings_advanced: Option<Advanced> = None;
if let Some(ref p) = opts.config_file {
if p.is_file() {
let path_resolved = p
.canonicalize()
.with_context(|| "error resolving toml config file path")?;
let file::Settings{ general, advanced } = file::Settings::read(&path_resolved)
.with_context(|| {"can not read toml config file because has invalid or unsupported format/options" })?;
config_file = Some(path_resolved);
if let Some(ref v) = general.host {
host = v.to_owned()
}
if let Some(v) = general.port {
port = v
}
if let Some(ref v) = general.root {
root = v.to_owned()
}
if let Some(ref v) = general.log_level {
log_level = v.name().to_lowercase();
}
if let Some(v) = general.cache_control_headers {
cache_control_headers = v
}
if let Some(v) = general.compression {
compression = v
}
if let Some(ref v) = general.page404 {
page404 = v.to_owned()
}
if let Some(ref v) = general.page50x {
page50x = v.to_owned()
}
if let Some(v) = general.http2 {
http2 = v
}
if let Some(ref v) = general.http2_tls_cert {
http2_tls_cert = v.to_owned()
}
if let Some(ref v) = general.http2_tls_key {
http2_tls_key = v.to_owned()
}
if let Some(v) = general.security_headers {
security_headers = v
}
if let Some(ref v) = general.cors_allow_origins {
cors_allow_origins = v.to_owned()
}
if let Some(ref v) = general.cors_allow_headers {
cors_allow_headers = v.to_owned()
}
if let Some(v) = general.directory_listing {
directory_listing = v
}
if let Some(v) = general.directory_listing_order {
directory_listing_order = v
}
if let Some(ref v) = general.basic_auth {
basic_auth = v.to_owned()
}
if let Some(v) = general.fd {
fd = Some(v)
}
if let Some(v) = general.threads_multiplier {
threads_multiplier = v
}
if let Some(v) = general.grace_period {
grace_period = v
}
if let Some(ref v) = general.page_fallback {
page_fallback = v.to_owned()
}
settings_advanced = Some(advanced)
}
}
Ok(Settings {
general: General {
host,
port,
root,
log_level,
config_file,
cache_control_headers,
compression,
page404,
page50x,
http2,
http2_tls_cert,
http2_tls_key,
security_headers,
cors_allow_origins,
cors_allow_headers,
directory_listing,
directory_listing_order,
basic_auth,
fd,
threads_multiplier,
grace_period,
page_fallback,
},
advanced: settings_advanced,
})
}
}
@@ -1,63 +1,67 @@
host = "::"
port = 80
root = "./public"
[general]
log-level = "error"
log-level = "debug"
cache-control-headers = true
compression = true
page404 = "404.html"
page50x = "50x.html"
http2 = true
http2-tls-cert = ""
http2-tls-key = ""
security-headers = true
cors-allow-origins = ""
directory-listing = false
basic-auth = ""
threads-multiplier = 1
grace-period = 0
page-fallback = ""
[advanced]
[[headers]]
source = "**/*.@(eot|otf|ttf|ttc|woff|font.css)"
headers = { Access-Control-Allow-Origin = "*", X-XSS-PROTECTION = "1; mode=block" }
[[headers]]
source = "**/*.@(jpg|jpeg|gif|png)"
[headers.headers]
Cache-Control = "max-age=7200"
Content-Security-Policy = "frame-ancestors 'self'"
[[headers]]
source = "404.html"
headers.Cache-Control = "max-age=300"