feat: custom http headers support via config file
resolves #30 #100
Diff
Cargo.lock | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
Cargo.toml | 1 +
src/custom_headers.rs | 22 ++++++++++++++++++++++
src/handler.rs | 14 +++++++++++---
src/lib.rs | 1 +
src/server.rs | 5 +++--
src/settings/file.rs | 6 +++---
src/settings/mod.rs | 36 +++++++++++++++++++++++++++++++-----
tests/toml/config.toml | 22 +++++++++++-----------
9 files changed, 133 insertions(+), 24 deletions(-)
@@ -9,6 +9,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -123,6 +132,15 @@ dependencies = [
]
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"memchr",
]
[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -312,6 +330,20 @@ dependencies = [
]
[[package]]
name = "globset"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
"serde",
]
[[package]]
name = "h2"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -732,6 +764,23 @@ dependencies = [
]
[[package]]
name = "regex"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -902,6 +951,7 @@ dependencies = [
"bytes",
"form_urlencoded",
"futures-util",
"globset",
"headers",
"http",
"http-serde",
@@ -55,6 +55,7 @@ serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_ignored = "0.1"
toml = "0.5"
http-serde = "1.1"
globset = { version = "0.4", features = ["serde1"] }
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.tikv-jemallocator]
version = "0.4"
@@ -0,0 +1,22 @@
use hyper::{Body, Response};
use crate::settings::HeadersOpt;
pub fn append_headers(
uri: &str,
multiple_headers: &Option<Vec<HeadersOpt>>,
resp: &mut Response<Body>,
) {
if let Some(multiple_headers) = multiple_headers {
for headers_entry in multiple_headers.iter() {
if headers_entry.source.is_match(uri) {
for (name, value) in &headers_entry.headers {
resp.headers_mut().insert(name, value.to_owned());
}
}
}
}
}
@@ -2,13 +2,13 @@ use hyper::{header::WWW_AUTHENTICATE, Body, Method, Request, Response, StatusCod
use std::{future::Future, path::PathBuf, sync::Arc};
use crate::{
basic_auth, compression, control_headers, cors, error_page, fallback_page, security_headers,
static_files,
basic_auth, compression, control_headers, cors, custom_headers, error_page, fallback_page,
security_headers, settings::AdvancedOpts, static_files, Error, Result,
};
use crate::{Error, Result};
pub struct RequestHandlerOpts {
pub root_dir: PathBuf,
pub compression: bool,
pub dir_listing: bool,
@@ -20,6 +20,9 @@ pub struct RequestHandlerOpts {
pub page50x: String,
pub page_fallback: String,
pub basic_auth: String,
pub advanced_opts: Option<AdvancedOpts>,
}
@@ -154,6 +157,11 @@ impl RequestHandler {
security_headers::append_headers(&mut resp);
}
if let Some(advanced) = &self.opts.advanced_opts {
custom_headers::append_headers(uri_path, &advanced.headers, &mut resp)
}
Ok(resp)
}
Err(status) => {
@@ -13,6 +13,7 @@ pub mod basic_auth;
pub mod compression;
pub mod control_headers;
pub mod cors;
pub mod custom_headers;
pub mod error_page;
pub mod fallback_page;
pub mod handler;
@@ -54,8 +54,8 @@ impl Server {
async fn start_server(self) -> Result {
let general = self.opts.general;
let advanced_opts = self.opts.advanced;
let log_level = &general.log_level.to_lowercase();
@@ -159,6 +159,7 @@ impl Server {
page50x,
page_fallback,
basic_auth,
advanced_opts,
}),
});
@@ -33,7 +33,7 @@ impl LogLevel {
#[serde(rename_all = "kebab-case")]
pub struct Headers {
pub source: String,
#[serde(with = "http_serde::header_map")]
#[serde(rename(deserialize = "headers"), with = "http_serde::header_map")]
pub headers: HeaderMap,
}
@@ -42,11 +42,11 @@ pub struct Headers {
#[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 {
@@ -1,19 +1,29 @@
use globset::{Glob, GlobMatcher};
use headers::HeaderMap;
use structopt::StructOpt;
use crate::{Context, Result};
mod cli;
mod file;
pub mod file;
use cli::General;
use file::Advanced;
pub struct AdvancedOpts {
pub headers: Option<Vec<HeadersOpt>>,
}
pub struct HeadersOpt {
pub source: GlobMatcher,
pub headers: HeaderMap,
}
pub struct Settings {
pub general: General,
pub advanced: Option<Advanced>,
pub advanced: Option<AdvancedOpts>,
}
impl Settings {
@@ -46,7 +56,7 @@ impl Settings {
let mut page_fallback = opts.page_fallback.to_owned();
let mut settings_advanced: Option<Advanced> = None;
let mut settings_advanced: Option<AdvancedOpts> = None;
@@ -127,7 +137,23 @@ impl Settings {
}
}
settings_advanced = settings.advanced
if let Some(advanced) = settings.advanced {
if let Some(multiple_headers) = advanced.headers {
let mut headers_vec: Vec<HeadersOpt> = Vec::new();
for headers_entry in multiple_headers.iter() {
let source = Glob::new(&headers_entry.source)?.compile_matcher();
headers_vec.push(HeadersOpt {
source,
headers: headers_entry.headers.to_owned(),
});
}
settings_advanced = Some(AdvancedOpts {
headers: Some(headers_vec),
});
}
}
}
}
@@ -6,7 +6,7 @@ port = 8087
root = "docker/public"
log-level = "debug"
log-level = "trace"
cache-control-headers = true
@@ -50,19 +50,19 @@ page-fallback = ""
[[headers]]
source = "**/*.@(eot|otf|ttf|ttc|woff|font.css)"
[[advanced.headers]]
source = "**/*.{js,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"
[[advanced.headers]]
source = "*.html"
[advanced.headers.headers]
Cache-Control = "public, max-age=36000"
Content-Security-Policy = "frame-ancestors 'self'"
Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
[[headers]]
source = "404.html"
headers.Cache-Control = "max-age=300"
[[advanced.headers]]
source = "**/*.{jpg,jpeg,png,ico,gif}"
headers.Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"