chore: preliminary toml manifest file
Diff
Cargo.lock | 41 ++++++++++++++-
Cargo.toml | 3 +-
src/config.rs | 13 ++++-
src/helpers.rs | 40 ++++++++++++-
src/lib.rs | 4 +-
src/manifest.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++-
tests/toml/config.toml | 66 ++++++++++++++++++++++-
7 files changed, 315 insertions(+), 1 deletion(-)
@@ -774,6 +774,35 @@ dependencies = [
]
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_ignored"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c2c7d39d14f2f2ea82239de71594782f186fd03501ac81f0ce08e674819ff2f"
dependencies = [
"serde",
]
[[package]]
name = "sha-1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -873,6 +902,8 @@ dependencies = [
"percent-encoding",
"pin-project",
"rustls-pemfile",
"serde",
"serde_ignored",
"signal-hook",
"signal-hook-tokio",
"structopt",
@@ -881,6 +912,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tokio-util",
"toml",
"tracing",
"tracing-subscriber",
]
@@ -1025,6 +1057,15 @@ dependencies = [
]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -51,6 +51,9 @@ tokio-util = { version = "0.7", default-features = false, features = ["io"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["smallvec", "parking_lot", "fmt", "ansi", "tracing-log"] }
form_urlencoded = "1.0"
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_ignored = "0.1"
toml = "0.5"
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.tikv-jemallocator]
version = "0.4"
@@ -1,3 +1,7 @@
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
@@ -171,4 +175,13 @@ pub struct Config {
#[structopt(long, short = "q", default_value = "0", env = "SERVER_GRACE_PERIOD")]
pub grace_period: u8,
#[structopt(
long,
short = "w",
default_value = "config.toml",
env = "SEVER_CONFIG_FILE"
)]
pub config_file: PathBuf,
}
@@ -1,7 +1,7 @@
use std::fs;
use std::path::{Path, PathBuf};
use crate::Result;
use crate::{Context, Result};
pub fn get_valid_dirpath<P: AsRef<Path>>(path: P) -> Result<PathBuf>
@@ -37,3 +37,41 @@ pub fn read_file_content(p: &str) -> String {
}
String::new()
}
pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
fs::read(path).with_context(|| format!("failed to read `{}`", path.display()))
}
pub fn read_file(path: &Path) -> Result<String> {
match String::from_utf8(read_bytes(path)?) {
Ok(s) => Ok(s),
Err(_) => bail!("path at `{}` was not valid utf-8", path.display()),
}
}
pub fn stringify(dst: &mut String, path: &serde_ignored::Path<'_>) {
use serde_ignored::Path;
match *path {
Path::Root => {}
Path::Seq { parent, index } => {
stringify(dst, parent);
if !dst.is_empty() {
dst.push('.');
}
dst.push_str(&index.to_string());
}
Path::Map { parent, ref key } => {
stringify(dst, parent);
if !dst.is_empty() {
dst.push('.');
}
dst.push_str(key);
}
Path::Some { parent }
| Path::NewtypeVariant { parent }
| Path::NewtypeStruct { parent } => stringify(dst, parent),
}
}
@@ -6,6 +6,9 @@
#[macro_use]
extern crate anyhow;
#[macro_use]
extern crate serde;
pub mod basic_auth;
pub mod compression;
pub mod config;
@@ -16,6 +19,7 @@ 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;
@@ -0,0 +1,149 @@
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, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Header {
pub key: String,
pub value: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Headers {
pub source: String,
pub headers: Option<Vec<Header>>,
}
#[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: String,
pub directory_listing: bool,
pub directory_listing_order: 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))
}
@@ -0,0 +1,66 @@
host = "::"
port = 80
root = "./public"
log-level = "error"
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 = ""
fd = ""
threads-multiplier = 1
grace-period = 0
page-fallback = ""
[[headers]]
source = "**/*.@(eot|otf|ttf|ttc|woff|font.css)"
[[headers.headers]]
key = "Access-Control-Allow-Origin"
value = "*"
[[headers.headers]]
key = "Access-Control-Allow-Origin"
value = "*"
[[headers.headers]]
key = "Access-Control-Allow-Origin"
value = "*"
[[headers]]
source = "**/*.@(jpg|jpeg|gif|png)"
[[headers.headers]]
key = "Cache-Control"
value = "max-age=7200"
[[headers]]
source = "404.html"
[[headers.headers]]
key = "Cache-Control"
value = "max-age=300"