feat: CORS support (resolves #18)
Diff
Cargo.lock | 11 +++++++++++
Cargo.toml | 1 +
README.md | 12 ++++++++----
Tasks.Dev.toml | 1 +
src/config.rs | 3 +++
src/main.rs | 6 ++++++
src/staticfiles.rs | 23 ++++++++++++++++++++++-
7 files changed, 52 insertions(+), 5 deletions(-)
@@ -295,6 +295,16 @@ dependencies = [
]
[[package]]
name = "iron-cors"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b02b8856c7f14e443c483e802cf0ce693f3bec19f49d2c9a242b18f88c9b70"
dependencies = [
"iron",
"log 0.4.8",
]
[[package]]
name = "iron-test"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -913,6 +923,7 @@ dependencies = [
"hyper",
"hyper-native-tls",
"iron",
"iron-cors",
"iron-test",
"iron_staticfile_middleware",
"log 0.4.8",
@@ -31,6 +31,7 @@ iron_staticfile_middleware = { git = "https://github.com/joseluisq/iron-staticfi
hyper-native-tls = "0.3"
nix = "0.14"
signal = "0.7"
iron-cors = "0.8"
[dev-dependencies]
openssl = { version = "0.10", features = ["vendored"] }
@@ -44,6 +44,7 @@ Server can be configured either via environment variables or their equivalent co
### Command-line arguments
@@ -64,22 +65,25 @@ OPTIONS:
--assets <assets>
Assets directory path for add cache headers functionality [env: SERVER_ASSETS=] [default: ./public/assets]
--host <host> Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]]
--cors-allow-origins <cors-allow-origins>
Specify a CORS list of allowed origin hosts separated by comas with no whitespaces. Host ports or protocols
aren't being checked. Use an asterisk (*) to allow any host [env: SERVER_CORS_ALLOW_ORIGINS=] [default: ]
--host <host> Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]]
--log-level <log-level>
Specify a logging level in lower case [env: SERVER_LOG_LEVEL=] [default: error]
--name <name> Name for server [env: SERVER_NAME=] [default: my-static-server]
--name <name> Name for server [env: SERVER_NAME=] [default: my-static-server]
--page404 <page404>
HTML file path for 404 errors. If path is not specified or simply don't exists then server will use a
generic HTML error message [env: SERVER_ERROR_PAGE_404=] [default: ./public/404.html]
--page50x <page50x>
HTML file path for 50x errors. If path is not specified or simply don't exists then server will use a
generic HTML error message [env: SERVER_ERROR_PAGE_50X=] [default: ./public/50x.html]
--port <port> Host port [env: SERVER_PORT=] [default: 80]
--port <port> Host port [env: SERVER_PORT=] [default: 80]
--root <root>
Root directory path of static files [env: SERVER_ROOT=] [default: ./public]
--tls <tls> Enables TLS/SSL support [env: SERVER_TLS=]
--tls <tls> Enables TLS/SSL support [env: SERVER_TLS=]
--tls-pkcs12 <tls-pkcs12>
A cryptographic identity PKCS #12 bundle file path containing a X509 certificate along with its
corresponding private key and chain of certificates to a trusted root [env: SERVER_TLS_PKCS12=] [default: ]
@@ -4,6 +4,7 @@ E_URL = "http://locahost"
SERVER_LOG_LEVEL = "trace"
SERVER_ROOT = "./public"
SERVER_ASSETS = "./public/assets"
SERVER_CORS_ALLOW_ORIGINS = "*"
[tasks.watch]
command = "cargo"
@@ -44,4 +44,7 @@ pub struct Options {
#[structopt(long, default_value = "error", env = "SERVER_LOG_LEVEL")]
pub log_level: String,
#[structopt(long, default_value = "", env = "SERVER_CORS_ALLOW_ORIGINS")]
pub cors_allow_origins: String,
}
@@ -47,6 +47,7 @@ fn main() {
assets_dir: opts.assets,
page_50x_path: opts.page50x,
page_404_path: opts.page404,
cors_allow_origins: opts.cors_allow_origins,
});
if opts.tls {
@@ -121,6 +122,7 @@ mod test {
assets_dir: opts.assets,
page_50x_path: opts.page50x,
page_404_path: opts.page404,
cors_allow_origins: "".to_string(),
});
let response = request::head("http://127.0.0.1/", Headers::new(), &files.handle())
@@ -143,6 +145,7 @@ mod test {
assets_dir: opts.assets,
page_50x_path: opts.page50x,
page_404_path: opts.page404,
cors_allow_origins: "".to_string(),
});
let res = request::head("http://127.0.0.1/", Headers::new(), &files.handle())
@@ -170,6 +173,7 @@ mod test {
assets_dir: assets.path().to_str().unwrap().to_string(),
page_50x_path: opts.page50x,
page_404_path: opts.page404,
cors_allow_origins: "".to_string(),
});
let res = request::head("http://127.0.0.1/", Headers::new(), &files.handle())
@@ -191,6 +195,7 @@ mod test {
assets_dir: opts.assets,
page_50x_path: opts.page50x,
page_404_path: opts.page404,
cors_allow_origins: "".to_string(),
});
let res = request::head("http://127.0.0.1/unknown", Headers::new(), &files.handle())
@@ -212,6 +217,7 @@ mod test {
assets_dir: opts.assets,
page_50x_path: opts.page50x,
page_404_path: opts.page404,
cors_allow_origins: "".to_string(),
});
let response = request::post("http://127.0.0.1/", Headers::new(), "", &files.handle())
@@ -1,10 +1,12 @@
use crate::error_page::ErrorPage;
use crate::gzip::GzipMiddleware;
use crate::helpers;
use crate::logger::Logger;
use crate::logger::{log_server, Logger};
use iron::prelude::*;
use iron_cors::CorsMiddleware;
use iron_staticfile_middleware::{Cache, GuessContentType, ModifyWith, Prefix, Staticfile};
use std::collections::HashSet;
use std::time::Duration;
@@ -17,6 +19,7 @@ pub struct StaticFilesOptions {
pub assets_dir: String,
pub page_50x_path: String,
pub page_404_path: String,
pub cors_allow_origins: String,
}
impl StaticFiles {
@@ -65,6 +68,24 @@ impl StaticFiles {
.parse()
.expect("Unable to create a default content type header");
let allowed_hosts = &self.opts.cors_allow_origins;
if !allowed_hosts.is_empty() {
log_server("CORS enabled");
log_server(&format!("Access-Control-Allow-Origin: {}", allowed_hosts));
if allowed_hosts == "*" {
chain.link_around(CorsMiddleware::with_allow_any());
} else {
let allowed_hosts = allowed_hosts
.split(',')
.map(|s| s.to_string())
.collect::<HashSet<_>>();
chain.link_around(CorsMiddleware::with_whitelist(allowed_hosts));
};
}
chain.link_after(ModifyWith::new(Cache::new(one_day)));
chain.link_after(Prefix::new(&[assets_dirname], Cache::new(one_year)));
chain.link_after(GuessContentType::new(default_content_type));