diff options
author | Melody Horn <melody@boringcactus.com> | 2022-05-01 19:59:33 -0600 |
---|---|---|
committer | Melody Horn <melody@boringcactus.com> | 2022-05-01 19:59:33 -0600 |
commit | f069a979ddc457ccd2d1e9dd26eca38031a86661 (patch) | |
tree | f7f8301fc40a1b0c71835c684560e8a6578cceeb | |
parent | 3817ec811d76b91d3d4924003a4d48d0cd43a576 (diff) | |
download | narchttpd-f069a979ddc457ccd2d1e9dd26eca38031a86661.tar.gz narchttpd-f069a979ddc457ccd2d1e9dd26eca38031a86661.zip |
kdl configconfig-kdl
-rw-r--r-- | Cargo.lock | 303 | ||||
-rw-r--r-- | Cargo.toml | 6 | ||||
-rw-r--r-- | src/main.rs | 178 | ||||
-rw-r--r-- | src/utils/proxy_child.rs | 1 | ||||
-rw-r--r-- | tests/config-example/narchttpd.kdl | 21 | ||||
-rw-r--r-- | tests/config-example/narchttpd.toml | 20 |
6 files changed, 402 insertions, 127 deletions
@@ -3,6 +3,30 @@ version = 3 [[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +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 = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -13,9 +37,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", @@ -40,6 +64,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -52,12 +97,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "chumsky" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" + +[[package]] name = "clap" version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -67,7 +124,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -119,6 +176,12 @@ dependencies = [ ] [[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] name = "h2" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -153,6 +216,12 @@ dependencies = [ ] [[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -239,12 +308,45 @@ dependencies = [ ] [[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + +[[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] +name = "knuffel" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f7a07459e9dc5d07f5dabfc2c2a965bf39195451eb785b61460c65f386eef" +dependencies = [ + "base64", + "chumsky", + "knuffel-derive", + "miette", + "thiserror", + "unicode-width", +] + +[[package]] +name = "knuffel-derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcbdb0b6f26a4e5ecb0dd9074a430398a41b2c1624c205bcc202541ddc15488" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -281,6 +383,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] +name = "miette" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8218047465aa2a0aa5ceca2b5065ae27b54f744c16bf9272fdbc39917362785f" +dependencies = [ + "atty", + "backtrace", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap 0.15.0", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d56e20719ef4fc7fd02960f72a2451a724e3c348627a941e2f37dda37b247" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +dependencies = [ + "adler", +] + +[[package]] name = "mio" version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -308,10 +450,10 @@ version = "0.1.0" dependencies = [ "async-trait", "hyper", - "serde", + "knuffel", + "miette", "structopt", "tokio", - "toml", ] [[package]] @@ -334,12 +476,27 @@ dependencies = [ ] [[package]] +name = "object" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +dependencies = [ + "memchr", +] + +[[package]] name = "once_cell" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] +name = "owo-colors" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" + +[[package]] name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -428,30 +585,33 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "regex" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] -name = "serde" -version = "1.0.132" +name = "regex-syntax" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" -dependencies = [ - "serde_derive", -] +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "serde_derive" -version = "1.0.132" +name = "rustc-demangle" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "signal-hook-registry" @@ -475,6 +635,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] name = "socket2" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -507,7 +673,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", @@ -515,10 +681,38 @@ dependencies = [ ] [[package]] +name = "supports-color" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" +dependencies = [ + "atty", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" +dependencies = [ + "atty", +] + +[[package]] +name = "supports-unicode" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" +dependencies = [ + "atty", +] + +[[package]] name = "syn" -version = "1.0.81" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -526,6 +720,16 @@ dependencies = [ ] [[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -535,6 +739,37 @@ dependencies = [ ] [[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "tokio" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -580,15 +815,6 @@ 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" @@ -621,6 +847,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] +name = "unicode-linebreak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" +dependencies = [ + "regex", +] + +[[package]] name = "unicode-segmentation" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6,9 +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" +async-trait = "0.1.53" hyper = { version = "0.14", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } +knuffel = "2.0.0" +miette = { version = "4.6.0", features = ["fancy"] } structopt = "0.3.25" tokio = { version = "1", features = ["full"] } -toml = "0.5.8" diff --git a/src/main.rs b/src/main.rs index f284b75..d99b672 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use std::convert::Infallible; -use std::fs::read_to_string; -use std::net::SocketAddr; +use std::fs; +use std::net::{Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; use hyper::service::{make_service_fn, service_fn}; use hyper::{header, Body, Request, Response, Server, StatusCode}; -use serde::Deserialize; +use miette::IntoDiagnostic; use structopt::StructOpt; mod utils; @@ -15,99 +15,139 @@ use utils::HttpHandler; #[derive(Debug, StructOpt)] struct Opt { - #[structopt(long, parse(from_os_str), default_value = "narchttpd.toml")] + #[structopt(long, parse(from_os_str), default_value = "narchttpd.kdl")] config_file: PathBuf, } -#[derive(Deserialize)] -#[serde(tag = "mode", rename_all = "kebab-case")] -enum DomainConfig { - Static { - root: PathBuf, - }, - - ProxyChild { - command: String, - in_dir: Option<PathBuf>, - port: u16, - }, +#[derive(Debug, knuffel::Decode)] +struct HttpConfig { + #[knuffel(argument)] + enabled: bool, + #[knuffel(property(name = "port"))] + port: u16, } -impl DomainConfig { +#[derive(Debug, knuffel::Decode)] +struct StaticDomain { + #[knuffel(child, unwrap(argument))] + root: PathBuf, +} + +impl StaticDomain { + fn handler(self) -> Box<dyn HttpHandler> { + let Self { root } = self; + Box::new(utils::serve_static::Params::new(root)) + } +} + +#[derive(Debug, knuffel::Decode)] +struct ProxyChildDomain { + #[knuffel(child, unwrap(argument))] + command: String, + #[knuffel(child, unwrap(argument))] + in_dir: Option<PathBuf>, + #[knuffel(child, unwrap(argument))] + port: u16, +} + +impl ProxyChildDomain { + fn handler(self) -> Box<dyn HttpHandler> { + let Self { + command, + in_dir, + port, + } = self; + Box::new(utils::proxy_child::ProxyChild::new(command, in_dir, port)) + } +} + +#[derive(Debug, knuffel::Decode)] +enum DomainType { + Static(StaticDomain), + ProxyChild(ProxyChildDomain), +} + +impl DomainType { 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)), + Self::Static(domain) => domain.handler(), + Self::ProxyChild(domain) => domain.handler(), } } } -#[derive(Deserialize)] +#[derive(Debug, knuffel::Decode)] +struct DomainConfig { + #[knuffel(arguments)] + domains: Vec<String>, + #[knuffel(children)] + config: Vec<DomainType>, +} + +#[derive(Debug, knuffel::Decode)] struct Config { - http_ports: Vec<u16>, - https_ports: Vec<u16>, - #[serde(flatten)] - domains: HashMap<String, DomainConfig>, + #[knuffel(child)] + http: HttpConfig, + #[knuffel(children(name = "domain"))] + domains: Vec<DomainConfig>, } #[tokio::main] -async fn main() { - let opt = Opt::from_args(); +async fn main() -> miette::Result<()> { + let opt: Opt = Opt::from_args(); - let config_data = read_to_string(&opt.config_file).expect("Config file not found"); + let config_data = fs::read_to_string(&opt.config_file).into_diagnostic()?; - let Config { - http_ports, - https_ports, - domains, - } = toml::from_str(&config_data).expect("Config file not valid"); + let Config { http, domains } = + knuffel::parse(&opt.config_file.to_string_lossy(), &config_data)?; - assert!(https_ports.is_empty(), "HTTPS is complicated oops"); + let http_port = http.port; let domains: HashMap<_, _> = domains .into_iter() - .map(|(domain, config)| (domain, Arc::new(config.handler()))) + .map(|DomainConfig { domains, config }| { + let [config]: [DomainType; 1] = config.try_into().unwrap(); + (domains, Arc::new(config.handler())) + }) + .flat_map(|(domains, handler)| { + domains + .into_iter() + .map(move |domain| (domain, Arc::clone(&handler))) + }) .collect(); - let do_response_domains = domains.clone(); + let domains = Arc::new(domains); // TODO learn hyper - 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 == req_domain { - eprintln!("request {:?} matched domain {}", req, domain); - // matched! - return handler.handle(req).await; - } + let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), http_port); + + let make_svc = make_service_fn(move |_conn| { + let domains = domains.clone(); + async move { + let domains = domains.clone(); + Ok::<_, Infallible>(service_fn(move |req: Request<Body>| { + let domains = domains.clone(); + async move { + let domains = domains.clone(); + let req_domain = req.headers().get(header::HOST).unwrap(); + let req_domain = req_domain.to_str().unwrap(); + match domains.get(req_domain) { + Some(handler) => { + eprintln!("request {:?} matched domain {}", req, req_domain); + Ok::<_, Infallible>(handler.handle(req).await) + } + None => Ok::<_, Infallible>( + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("Not Found")) + .unwrap(), + ), + } + } + })) } - Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Body::from("Not Found")) - .unwrap() - }; - let addr = http_ports.into_iter().flat_map(|port| { - ["127.0.0.1", "::1"] - .iter() - .map(move |ip| SocketAddr::new(ip.parse().unwrap(), port)) - }); - - let make_svc = make_service_fn(move |_conn| async move { - Ok::<_, Infallible>(service_fn(move |req| async move { - let response = do_response(req).await; - Ok::<_, Infallible>(response) - })) }); - // TODO uhh - let mut addr = addr; - let addr = addr.nth(0).unwrap(); let server = Server::bind(&addr).serve(make_svc); - if let Err(e) = server.await { - eprintln!("server error: {}", e); - } + server.await.into_diagnostic() } diff --git a/src/utils/proxy_child.rs b/src/utils/proxy_child.rs index cc35298..8ee0317 100644 --- a/src/utils/proxy_child.rs +++ b/src/utils/proxy_child.rs @@ -1,6 +1,5 @@ use std::path::PathBuf; use std::process::{Child as ChildProcess, Command}; -use std::rc::Rc; use std::sync::Arc; use hyper::http::uri::Scheme; diff --git a/tests/config-example/narchttpd.kdl b/tests/config-example/narchttpd.kdl new file mode 100644 index 0000000..ca24846 --- /dev/null +++ b/tests/config-example/narchttpd.kdl @@ -0,0 +1,21 @@ +http true port=1337 + +domain "domain.test" "alternate-domain.test" { + static { + root "./domain.test" + } +} + +domain "sub.domain.test" { + static { + root "./sub.domain.test" + } +} + +domain "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 deleted file mode 100644 index a5876c6..0000000 --- a/tests/config-example/narchttpd.toml +++ /dev/null @@ -1,20 +0,0 @@ -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 |