aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2021-12-24 16:25:51 -0700
committerMelody Horn <melody@boringcactus.com>2021-12-24 16:25:51 -0700
commit3817ec811d76b91d3d4924003a4d48d0cd43a576 (patch)
tree93cd9ac73e08c9d4414bb312f7a01f104581a160
parentc6d12d80babd9f4bd5692cf74ab9d6e9278859d1 (diff)
downloadnarchttpd-config-toml.tar.gz
narchttpd-config-toml.zip
rust is a myth. it doesn't existconfig-toml
-rw-r--r--Cargo.lock106
-rw-r--r--Cargo.toml4
-rw-r--r--src/main.rs121
-rw-r--r--src/utils.rs8
-rw-r--r--src/utils/proxy_child.rs69
-rw-r--r--src/utils/serve_static.rs102
-rw-r--r--tests/config-example/narchttpd.rhai16
-rw-r--r--tests/config-example/narchttpd.toml20
8 files changed, 195 insertions, 251 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 78bbea7..c52efd0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,23 +3,23 @@
version = 3
[[package]]
-name = "ahash"
-version = "0.7.6"
+name = "ansi_term"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
- "getrandom",
- "once_cell",
- "version_check",
+ "winapi",
]
[[package]]
-name = "ansi_term"
-version = "0.11.0"
+name = "async-trait"
+version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
dependencies = [
- "winapi",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -119,17 +119,6 @@ dependencies = [
]
[[package]]
-name = "getrandom"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
name = "h2"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -317,10 +306,12 @@ dependencies = [
name = "narchttpd"
version = "0.1.0"
dependencies = [
+ "async-trait",
"hyper",
- "rhai",
+ "serde",
"structopt",
"tokio",
+ "toml",
]
[[package]]
@@ -333,15 +324,6 @@ dependencies = [
]
[[package]]
-name = "num-traits"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -446,25 +428,25 @@ dependencies = [
]
[[package]]
-name = "rhai"
-version = "1.1.2"
+name = "scopeguard"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b9394df571d413a865159d9e73df2e16e270c112bddc15538944c076469760a"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.132"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
dependencies = [
- "ahash",
- "instant",
- "num-traits",
- "rhai_codegen",
- "smallvec",
- "smartstring",
- "unicode-xid",
+ "serde_derive",
]
[[package]]
-name = "rhai_codegen"
-version = "1.1.0"
+name = "serde_derive"
+version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2a81e59b9782e8b85052aaed305d71dfcdec1bfa53e774c19ac1085307cbf39"
+checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276"
dependencies = [
"proc-macro2",
"quote",
@@ -472,12 +454,6 @@ dependencies = [
]
[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-
-[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -499,15 +475,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
-name = "smartstring"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31aa6a31c0c2b21327ce875f7e8952322acfcfd0c27569a6e18a647281352c9b"
-dependencies = [
- "static_assertions",
-]
-
-[[package]]
name = "socket2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -518,12 +485,6 @@ dependencies = [
]
[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
-[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -619,6 +580,15 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "tower-service"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -691,12 +661,6 @@ dependencies = [
]
[[package]]
-name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
-
-[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 44cd718..194769e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+async-trait = "0.1.52"
hyper = { version = "0.14", features = ["full"] }
-rhai = { version = "1.1", features = ["unicode-xid-ident", "no_closure", "internals"] }
+serde = { version = "1.0", features = ["derive"] }
structopt = "0.3.25"
tokio = { version = "1", features = ["full"] }
+toml = "0.5.8"
diff --git a/src/main.rs b/src/main.rs
index 0951518..f284b75 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,86 +1,87 @@
+use std::collections::HashMap;
use std::convert::Infallible;
+use std::fs::read_to_string;
use std::net::SocketAddr;
use std::path::PathBuf;
-use std::rc::Rc;
use std::sync::Arc;
use hyper::service::{make_service_fn, service_fn};
use hyper::{header, Body, Request, Response, Server, StatusCode};
-use rhai::{Dynamic, Engine, FnPtr, Map, NativeCallContext, Scope};
+use serde::Deserialize;
use structopt::StructOpt;
mod utils;
+use utils::HttpHandler;
#[derive(Debug, StructOpt)]
struct Opt {
- #[structopt(long, parse(from_os_str), default_value = "narchttpd.rhai")]
- config_script: PathBuf,
+ #[structopt(long, parse(from_os_str), default_value = "narchttpd.toml")]
+ config_file: PathBuf,
}
-fn make_engine() -> Engine {
- let mut engine = Engine::new();
- engine.register_type_with_name::<Rc<Request<Body>>>("Request");
- engine.register_type::<utils::serve_static::Params>();
- engine.register_fn(
- "handle_request_serve_static",
- utils::serve_static::handle_request,
- );
- engine.register_fn("serve_static", utils::serve_static::serve_static);
- engine.register_fn(
- "handle_request_proxy_child",
- utils::proxy_child::handle_request,
- );
- engine.register_fn("proxy_child", utils::proxy_child::proxy_child);
- engine
+#[derive(Deserialize)]
+#[serde(tag = "mode", rename_all = "kebab-case")]
+enum DomainConfig {
+ Static {
+ root: PathBuf,
+ },
+
+ ProxyChild {
+ command: String,
+ in_dir: Option<PathBuf>,
+ port: u16,
+ },
+}
+
+impl DomainConfig {
+ fn handler(self) -> Box<dyn HttpHandler> {
+ match self {
+ Self::Static { root } => Box::new(utils::serve_static::Params::new(root)),
+ Self::ProxyChild {
+ command,
+ in_dir,
+ port,
+ } => Box::new(utils::proxy_child::ProxyChild::new(command, in_dir, port)),
+ }
+ }
}
-fn get_config_scope(engine: &Engine, opt: &Opt) -> Scope<'static> {
- let mut ast = engine.compile_file(opt.config_script.clone()).unwrap();
-
- let mut scope = Scope::new();
- scope.push("http_ports", [80]);
- scope.push("https_ports", [443]);
- scope.push("domains", Map::new());
- let export_ast = engine
- .compile("export http_ports, https_ports, domains;")
- .unwrap();
- ast.combine(export_ast);
- let _: () = engine.eval_ast_with_scope(&mut scope, &ast).unwrap();
- scope
+#[derive(Deserialize)]
+struct Config {
+ http_ports: Vec<u16>,
+ https_ports: Vec<u16>,
+ #[serde(flatten)]
+ domains: HashMap<String, DomainConfig>,
}
#[tokio::main]
async fn main() {
- let opt = Arc::new(Opt::from_args());
+ let opt = Opt::from_args();
- let engine = make_engine();
- let scope = get_config_scope(&engine, &opt);
+ let config_data = read_to_string(&opt.config_file).expect("Config file not found");
- let http_ports: rhai::Array = scope.get_value("http_ports").unwrap();
- let http_ports: Vec<u16> = http_ports
- .into_iter()
- .map(|x| x.as_int().unwrap() as u16)
- .collect();
- let https_ports: rhai::Array = scope.get_value("https_ports").unwrap();
- let https_ports: Vec<u16> = https_ports
- .into_iter()
- .map(|x| x.as_int().unwrap() as u16)
- .collect();
+ let Config {
+ http_ports,
+ https_ports,
+ domains,
+ } = toml::from_str(&config_data).expect("Config file not valid");
assert!(https_ports.is_empty(), "HTTPS is complicated oops");
+ let domains: HashMap<_, _> = domains
+ .into_iter()
+ .map(|(domain, config)| (domain, Arc::new(config.handler())))
+ .collect();
+ let do_response_domains = domains.clone();
+
// TODO learn hyper
- let do_response = move |ctx: &NativeCallContext, domains: Map, req: Request<Body>| {
- for (domain, handler) in &domains {
+ let do_response = move |req: Request<Body>| async move {
+ for (domain, handler) in do_response_domains.clone() {
let req_domain = req.headers().get(header::HOST).unwrap();
- if domain.as_str() == req_domain {
+ if &domain == req_domain {
eprintln!("request {:?} matched domain {}", req, domain);
// matched!
- let handler: FnPtr = handler.clone_cast();
- let args = [Dynamic::from(Rc::new(req))];
- let result = handler.call_dynamic(ctx, None, args).unwrap();
- let result: Rc<Response<Body>> = result.cast();
- return Rc::try_unwrap(result).unwrap();
+ return handler.handle(req).await;
}
}
Response::builder()
@@ -96,19 +97,7 @@ async fn main() {
let make_svc = make_service_fn(move |_conn| async move {
Ok::<_, Infallible>(service_fn(move |req| async move {
- let handle = tokio::runtime::Handle::current();
- let response = handle
- .spawn_blocking(move || {
- let opt = Opt::from_args();
- let engine = make_engine();
- let scope = get_config_scope(&engine, &opt);
- let request_handler_context =
- NativeCallContext::new(&engine, "handle_request", &[]);
- let domains: Map = scope.get_value("domains").unwrap();
- do_response(&request_handler_context, domains, req)
- })
- .await
- .unwrap();
+ let response = do_response(req).await;
Ok::<_, Infallible>(response)
}))
});
diff --git a/src/utils.rs b/src/utils.rs
index 0bd5c2e..ebc21f2 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,2 +1,10 @@
+use async_trait::async_trait;
+use hyper::{Body, Request, Response};
+
pub mod proxy_child;
pub mod serve_static;
+
+#[async_trait]
+pub trait HttpHandler: Send + Sync {
+ async fn handle(&self, request: Request<Body>) -> Response<Body>;
+}
diff --git a/src/utils/proxy_child.rs b/src/utils/proxy_child.rs
index d7a021d..cc35298 100644
--- a/src/utils/proxy_child.rs
+++ b/src/utils/proxy_child.rs
@@ -1,9 +1,12 @@
+use std::path::PathBuf;
use std::process::{Child as ChildProcess, Command};
use std::rc::Rc;
+use std::sync::Arc;
use hyper::http::uri::Scheme;
use hyper::{header, Body, Client, Request, Response};
-use rhai::{Dynamic, FnPtr, Map};
+
+use super::{async_trait, HttpHandler};
pub struct KillOnDrop(ChildProcess);
@@ -15,60 +18,48 @@ impl Drop for KillOnDrop {
#[derive(Clone)]
pub struct ProxyChild {
- process: Rc<KillOnDrop>,
+ process: Arc<KillOnDrop>,
port: u16,
}
impl ProxyChild {
- fn new(params: Map) -> Self {
- let command_line = params["command"].clone().into_immutable_string().unwrap();
- let port = params["port"].as_int().unwrap();
+ pub fn new(command_line: String, in_dir: Option<PathBuf>, port: u16) -> Self {
let mut command_line = command_line.split(" ");
let command = command_line.next().unwrap();
let mut child = Command::new(command);
child.args(command_line);
- if let Some(cwd) = params.get("in_dir") {
- let cwd = cwd.clone().into_immutable_string().unwrap();
- let cwd: &str = cwd.as_ref();
+ if let Some(cwd) = in_dir {
child.current_dir(cwd);
}
let child = child.spawn().unwrap();
Self {
- process: Rc::new(KillOnDrop(child)),
+ process: Arc::new(KillOnDrop(child)),
port: port as u16,
}
}
}
-pub fn handle_request(child: &mut ProxyChild, request: Rc<Request<Body>>) -> Rc<Response<Body>> {
- let ProxyChild { port, .. } = child;
- let mut request_uri = request.uri().clone().into_parts();
- // TODO ipv6 loopback?
- request_uri.authority = Some(format!("127.0.0.1:{}", port).parse().unwrap());
- request_uri.scheme = Some(Scheme::HTTP);
- let mut proxy_request = Request::builder()
- .method(request.method())
- .uri(request_uri)
- .header(header::HOST, request.headers()[header::HOST].clone());
- proxy_request.headers_mut().unwrap().extend(
- request
- .headers()
- .iter()
- .map(|(x, y)| (x.clone(), y.clone())),
- );
- // TODO handle nonempty body
- let proxy_request = proxy_request.body(Body::empty()).unwrap();
- let response = async {
+#[async_trait]
+impl HttpHandler for ProxyChild {
+ async fn handle(&self, request: Request<Body>) -> Response<Body> {
+ let ProxyChild { port, .. } = self;
+ let mut request_uri = request.uri().clone().into_parts();
+ // TODO ipv6 loopback?
+ request_uri.authority = Some(format!("127.0.0.1:{}", port).parse().unwrap());
+ request_uri.scheme = Some(Scheme::HTTP);
+ let mut proxy_request = Request::builder()
+ .method(request.method())
+ .uri(request_uri)
+ .header(header::HOST, request.headers()[header::HOST].clone());
+ proxy_request.headers_mut().unwrap().extend(
+ request
+ .headers()
+ .iter()
+ .map(|(x, y)| (x.clone(), y.clone())),
+ );
+ // TODO handle nonempty body
+ let proxy_request = proxy_request.body(Body::empty()).unwrap();
let client = Client::new();
- Rc::new(client.request(proxy_request).await.unwrap())
- };
- let runtime = tokio::runtime::Handle::current();
- runtime.block_on(response)
-}
-
-pub fn proxy_child(params: Map) -> FnPtr {
- let child = ProxyChild::new(params);
- let mut result = FnPtr::new("handle_request_proxy_child").unwrap();
- result.add_curry(Dynamic::from(child));
- result
+ client.request(proxy_request).await.unwrap()
+ }
}
diff --git a/src/utils/serve_static.rs b/src/utils/serve_static.rs
index 2ec0a90..c087c21 100644
--- a/src/utils/serve_static.rs
+++ b/src/utils/serve_static.rs
@@ -1,75 +1,61 @@
use std::fs::File;
use std::io::{ErrorKind, Read};
use std::path::PathBuf;
-use std::rc::Rc;
use hyper::{Body, Request, Response, StatusCode};
-use rhai::{Dynamic, FnPtr, Map};
+
+use super::{async_trait, HttpHandler};
#[derive(Clone, Debug)]
pub struct Params {
root: PathBuf,
}
-#[derive(Debug)]
-pub struct KeyError(&'static str);
-
-impl TryFrom<Map> for Params {
- type Error = KeyError;
-
- fn try_from(mut value: Map) -> Result<Self, Self::Error> {
- let root = value
- .remove("root")
- .and_then(|value| value.into_immutable_string().ok())
- .ok_or(KeyError("root"))?;
- let root: PathBuf = root.parse().unwrap();
+impl Params {
+ pub fn new(root: PathBuf) -> Self {
let root = root.canonicalize().unwrap();
- Ok(Self { root })
+ Self { root }
}
}
-pub fn handle_request(params: &mut Params, request: Rc<Request<Body>>) -> Rc<Response<Body>> {
- let Params { root } = params;
- eprintln!("handling request {:?}", request);
- let request_path = request.uri().path();
- let request_path = request_path.trim_start_matches("/");
- let target = root.join(request_path);
- let target = if target.is_dir() {
- target.join("index.html")
- } else {
- target
- };
- eprintln!("target is {}", target.display());
- let response = if target.ancestors().all(|ancestor| ancestor != root) {
- // oops! all directory traversal
- Response::builder()
- .status(StatusCode::BAD_REQUEST)
- .body(Body::empty())
- } else {
- let file = File::open(target);
- match file {
- Ok(mut file) => {
- let mut result = Vec::new();
- file.read_to_end(&mut result).unwrap();
- Response::builder().body(Body::from(result))
- }
- Err(err) => {
- let status_code = match err.kind() {
- ErrorKind::NotFound => StatusCode::NOT_FOUND,
- _ => StatusCode::BAD_REQUEST,
- };
- Response::builder()
- .status(status_code)
- .body(Body::from(status_code.canonical_reason().unwrap_or("")))
+#[async_trait]
+impl HttpHandler for Params {
+ async fn handle(&self, request: Request<Body>) -> Response<Body> {
+ let Params { root } = self;
+ eprintln!("handling request {:?}", request);
+ let request_path = request.uri().path();
+ let request_path = request_path.trim_start_matches("/");
+ let target = root.join(request_path);
+ let target = if target.is_dir() {
+ target.join("index.html")
+ } else {
+ target
+ };
+ eprintln!("target is {}", target.display());
+ let response = if target.ancestors().all(|ancestor| ancestor != root) {
+ // oops! all directory traversal
+ Response::builder()
+ .status(StatusCode::BAD_REQUEST)
+ .body(Body::empty())
+ } else {
+ let file = File::open(target);
+ match file {
+ Ok(mut file) => {
+ let mut result = Vec::new();
+ file.read_to_end(&mut result).unwrap();
+ Response::builder().body(Body::from(result))
+ }
+ Err(err) => {
+ let status_code = match err.kind() {
+ ErrorKind::NotFound => StatusCode::NOT_FOUND,
+ _ => StatusCode::BAD_REQUEST,
+ };
+ Response::builder()
+ .status(status_code)
+ .body(Body::from(status_code.canonical_reason().unwrap_or("")))
+ }
}
- }
- };
- Rc::new(response.unwrap())
-}
-
-pub fn serve_static(params: Map) -> FnPtr {
- let params: Params = params.try_into().unwrap();
- let mut result = FnPtr::new("handle_request_serve_static").unwrap();
- result.add_curry(Dynamic::from(params));
- result
+ };
+ response.unwrap()
+ }
}
diff --git a/tests/config-example/narchttpd.rhai b/tests/config-example/narchttpd.rhai
deleted file mode 100644
index 713dc3e..0000000
--- a/tests/config-example/narchttpd.rhai
+++ /dev/null
@@ -1,16 +0,0 @@
-http_ports = [1337];
-https_ports = [];
-
-let sample = serve_static(#{
- root: "./domain.test",
-});
-domains["domain.test"] = sample;
-domains["alternate-domain.test"] = sample;
-domains["sub.domain.test"] = serve_static(#{
- root: "./sub.domain.test",
-});
-domains["dynamic.test"] = proxy_child(#{
- command: "python -m http.server 6970",
- in_dir: "./dynamic.test",
- port: 6970,
-});
diff --git a/tests/config-example/narchttpd.toml b/tests/config-example/narchttpd.toml
new file mode 100644
index 0000000..a5876c6
--- /dev/null
+++ b/tests/config-example/narchttpd.toml
@@ -0,0 +1,20 @@
+http_ports = [1337]
+https_ports = []
+
+["domain.test"]
+mode = "static"
+root = "./domain.test"
+
+["alternate-domain.test"]
+mode = "static"
+root = "./domain.test"
+
+["sub.domain.test"]
+mode = "static"
+root = "./sub.domain.test"
+
+["dynamic.test"]
+mode = "proxy-child"
+command = "python -m http.server 6970"
+in_dir = "./dynamic.test"
+port = 6970