feat: cors support
Diff
src/bin/server.rs | 243 +++++++++++++++++++++++++++++++++---------------------
src/core/cache.rs | 2 +-
src/core/config.rs | 19 ++--
src/core/cors.rs | 36 ++++++++-
src/core/mod.rs | 1 +-
5 files changed, 201 insertions(+), 100 deletions(-)
@@ -7,7 +7,6 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
extern crate static_web_server;
use structopt::StructOpt;
use tracing::warn;
use warp::Filter;
use self::static_web_server::core::*;
@@ -16,115 +15,171 @@ use self::static_web_server::core::*;
async fn server(opts: config::Options) -> Result {
logger::init(&opts.log_level)?;
let host = opts.host.parse::<std::net::IpAddr>()?;
let port = opts.port;
let root_dir = helpers::get_valid_dirpath(opts.root)?;
let page404 = helpers::read_file_content(opts.page404.as_ref());
let page50x = helpers::read_file_content(opts.page50x.as_ref());
let page404_a = page404.clone();
let page50x_a = page50x.clone();
let public_head = warp::head().and(
warp::fs::dir(root_dir.clone())
.map(cache::control_headers)
.with(warp::trace::request())
.recover(move |rej| {
let page404_a = page404_a.clone();
let page50x_a = page50x_a.clone();
async move { rejection::handle_rejection(page404_a, page50x_a, rej).await }
}),
);
let page404_b = page404.clone();
let page50x_b = page50x.clone();
let public_get_default = warp::get().and(
warp::fs::dir(root_dir.clone())
.map(cache::control_headers)
.with(warp::trace::request())
.recover(move |rej| {
let page404_b = page404_b.clone();
let page50x_b = page50x_b.clone();
async move { rejection::handle_rejection(page404_b, page50x_b, rej).await }
}),
);
let (cors_filter, cors_allowed_origins) =
cors::get_opt_cors_filter(opts.cors_allow_origins.as_ref());
let base_dir_filter = warp::fs::dir(root_dir.clone())
.map(cache::control_headers)
.with(warp::trace::request())
.recover(move |rej| {
let page404_a = page404_a.clone();
let page50x_a = page50x_a.clone();
async move { rejection::handle_rejection(page404_a, page50x_a, rej).await }
});
let host = opts.host.parse::<std::net::IpAddr>()?;
let port = opts.port;
let public_head = warp::head().and(base_dir_filter.clone());
let public_get_default = warp::get().and(base_dir_filter.clone());
match opts.compression.as_ref() {
"brotli" => tokio::task::spawn(
warp::serve(
public_head.or(warp::get()
.and(cache::accept_encoding("br"))
.and(
warp::fs::dir(root_dir)
.map(cache::control_headers)
.with(warp::trace::request())
.with(warp::compression::brotli(true))
.recover(move |rej| {
let page404_c = page404.clone();
let page50x_c = page50x.clone();
async move {
rejection::handle_rejection(page404_c, page50x_c, rej).await
}
}),
)
.or(public_get_default)),
)
.run((host, port)),
),
"deflate" => tokio::task::spawn(
warp::serve(
public_head.or(warp::get()
.and(cache::accept_encoding("deflate"))
.and(
warp::fs::dir(root_dir)
.map(cache::control_headers)
.with(warp::trace::request())
.with(warp::compression::deflate(true))
.recover(move |rej| {
let page404_c = page404.clone();
let page50x_c = page50x.clone();
async move {
rejection::handle_rejection(page404_c, page50x_c, rej).await
}
}),
)
.or(public_get_default)),
)
.run((host, port)),
),
"gzip" => tokio::task::spawn(
warp::serve(
public_head.or(warp::get()
.and(cache::accept_encoding("gzip"))
.and(
warp::fs::dir(root_dir)
.map(cache::control_headers)
.with(warp::trace::request())
.with(warp::compression::gzip(true))
.recover(move |rej| {
let page404_c = page404.clone();
let page50x_c = page50x.clone();
async move {
rejection::handle_rejection(page404_c, page50x_c, rej).await
}
}),
)
.or(public_get_default)),
)
.run((host, port)),
),
_ => tokio::task::spawn(warp::serve(public_head.or(public_get_default)).run((host, port))),
"brotli" => tokio::task::spawn(async move {
let with_dir = warp::fs::dir(root_dir)
.map(cache::control_headers)
.with(warp::trace::request())
.with(warp::compression::brotli(true))
.recover(move |rej| {
let page404 = page404.clone();
let page50x = page50x.clone();
async move { rejection::handle_rejection(page404, page50x, rej).await }
});
if let Some(cors_filter) = cors_filter {
tracing::info!(
cors_enabled = ?true,
allowed_origins = ?cors_allowed_origins
);
warp::serve(
public_head.with(cors_filter.clone()).or(warp::get()
.and(cache::has_accept_encoding("br"))
.and(with_dir)
.with(cors_filter.clone())
.or(public_get_default.with(cors_filter))),
)
.run((host, port))
.await
} else {
warp::serve(
public_head.or(warp::get()
.and(cache::has_accept_encoding("br"))
.and(with_dir)
.or(public_get_default)),
)
.run((host, port))
.await
}
}),
"deflate" => tokio::task::spawn(async move {
let with_dir = warp::fs::dir(root_dir)
.map(cache::control_headers)
.with(warp::trace::request())
.with(warp::compression::deflate(true))
.recover(move |rej| {
let page404 = page404.clone();
let page50x = page50x.clone();
async move { rejection::handle_rejection(page404, page50x, rej).await }
});
if let Some(cors_filter) = cors_filter {
tracing::info!(
cors_enabled = ?true,
allowed_origins = ?cors_allowed_origins
);
warp::serve(
public_head.with(cors_filter.clone()).or(warp::get()
.and(cache::has_accept_encoding("deflate"))
.and(with_dir)
.with(cors_filter.clone())
.or(public_get_default.with(cors_filter))),
)
.run((host, port))
.await
} else {
warp::serve(
public_head.or(warp::get()
.and(cache::has_accept_encoding("deflate"))
.and(with_dir)
.or(public_get_default)),
)
.run((host, port))
.await
}
}),
"gzip" => tokio::task::spawn(async move {
let with_dir = warp::fs::dir(root_dir)
.map(cache::control_headers)
.with(warp::trace::request())
.with(warp::compression::gzip(true))
.recover(move |rej| {
let page404 = page404.clone();
let page50x = page50x.clone();
async move { rejection::handle_rejection(page404, page50x, rej).await }
});
if let Some(cors_filter) = cors_filter {
tracing::info!(
cors_enabled = ?true,
allowed_origins = ?cors_allowed_origins
);
warp::serve(
public_head.with(cors_filter.clone()).or(warp::get()
.and(cache::has_accept_encoding("gzip"))
.and(with_dir)
.with(cors_filter.clone())
.or(public_get_default.with(cors_filter))),
)
.run((host, port))
.await
} else {
warp::serve(
public_head.or(warp::get()
.and(cache::has_accept_encoding("gzip"))
.and(with_dir)
.or(public_get_default)),
)
.run((host, port))
.await
}
}),
_ => tokio::task::spawn(async move {
if let Some(cors_filter) = cors_filter {
tracing::info!(
cors_enabled = ?true,
allowed_origins = ?cors_allowed_origins
);
let public_get_default = warp::get()
.and(base_dir_filter.clone())
.with(cors_filter.clone());
warp::serve(public_head.or(public_get_default.with(cors_filter)))
.run((host, port))
.await
} else {
warp::serve(public_head.or(public_get_default))
.run((host, port))
.await
}
}),
};
signals::wait(|sig: signals::Signal| {
let code = signals::as_int(sig);
warn!("Signal {} caught. Server execution exited.", code);
tracing::warn!("Signal {} caught. Server execution exited.", code);
std::process::exit(code)
});
@@ -38,7 +38,7 @@ fn duration(n: u64) -> u32 {
}
pub fn accept_encoding(
pub fn has_accept_encoding(
val: &'static str,
) -> impl warp::Filter<Extract = (), Error = warp::Rejection> + Copy {
warp::header::contains("accept-encoding", val)
@@ -3,7 +3,7 @@ use structopt::StructOpt;
#[derive(Debug, StructOpt)]
pub struct Options {
#[structopt(long, short = "s", default_value = "::", env = "SERVER_HOST")]
#[structopt(long, short = "a", default_value = "::", env = "SERVER_HOST")]
pub host: String,
@@ -13,7 +13,7 @@ pub struct Options {
#[structopt(
long,
short = "t",
short = "n",
default_value = "8",
env = "SERVER_THREADS_MULTIPLIER"
)]
@@ -23,7 +23,7 @@ pub struct Options {
pub threads_multiplier: usize,
#[structopt(long, short = "r", default_value = "./public", env = "SERVER_ROOT")]
#[structopt(long, short = "d", default_value = "./public", env = "SERVER_ROOT")]
pub root: String,
@@ -43,12 +43,21 @@ pub struct Options {
pub page404: String,
#[structopt(long, short = "c", default_value = "gzip", env = "SERVER_COMPRESSION")]
#[structopt(long, short = "x", default_value = "gzip", env = "SERVER_COMPRESSION")]
pub compression: String,
#[structopt(long, short = "l", default_value = "error", env = "SERVER_LOG_LEVEL")]
#[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,
}
@@ -0,0 +1,36 @@
use std::collections::HashSet;
use warp::filters::cors::Builder;
pub fn get_opt_cors_filter(origins: &str) -> (Option<Builder>, String) {
let mut cors_allowed_hosts = String::new();
let cors_filter = if origins.is_empty() {
None
} else if origins == "*" {
cors_allowed_hosts = origins.into();
Some(
warp::cors()
.allow_any_origin()
.allow_methods(vec!["GET", "HEAD", "OPTIONS"]),
)
} else {
cors_allowed_hosts = origins.into();
let hosts = cors_allowed_hosts
.split(',')
.map(|s| s.trim().as_ref())
.collect::<HashSet<_>>();
if hosts.is_empty() {
cors_allowed_hosts = hosts.into_iter().collect::<Vec<&str>>().join(", ");
None
} else {
Some(
warp::cors()
.allow_origins(hosts)
.allow_methods(vec!["GET", "HEAD", "OPTIONS"]),
)
}
};
(cors_filter, cors_allowed_hosts)
}
@@ -1,5 +1,6 @@
pub mod cache;
pub mod config;
pub mod cors;
pub mod helpers;
pub mod logger;
pub mod rejection;