diff --git a/Cargo.lock b/Cargo.lock index dd9f620..5c3ee57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -597,8 +597,11 @@ dependencies = [ "clap", "futures", "gix", + "handlebars", "hex", + "mime_guess", "mockall", + "rust-embed", "serde", "serde_json", "sha2", @@ -1251,6 +1254,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlebars" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +dependencies = [ + "log", + "pest", + "pest_derive", + "rust-embed", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1831,6 +1849,50 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pest" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "pest_meta" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -2147,6 +2209,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-embed" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 1.0.109", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustix" version = "0.37.18" @@ -2698,6 +2794,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + [[package]] name = "uluru" version = "3.0.0" diff --git a/Cargo.toml b/Cargo.toml index c30a821..6369d3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,6 @@ signal-hook = "0.3.15" futures = "0.3.28" signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ]} clap = { version = "4.2.7", features = ["derive", "env"] } +handlebars = { version = "4.3.7", features = [ "rust-embed" ]} +rust-embed = "6.6.1" +mime_guess = "2.0.4" diff --git a/src/main.rs b/src/main.rs index 5091d21..e0af841 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,7 +65,7 @@ async fn main() { let manager = gateway::domain::manager::new(origin_store, domain_config_store, domain_checker); - let service = gateway::service::new(manager); + let service = gateway::service::new(manager).expect("service initialized"); let (addr, server) = warp::serve(service).bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), async { diff --git a/src/service.rs b/src/service.rs index 78cda89..6ecc626 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,5 +1,12 @@ +use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::sync; use warp::Filter; +use crate::domain; + +pub mod http_tpl; + /* * POST /domain/init (domain, config) -> token * POST /domain/config (domain, config) @@ -7,11 +14,55 @@ use warp::Filter; * GET /domains */ -pub fn new( - _manager: impl crate::domain::manager::Manager, -) -> impl warp::Filter + Clone { - warp::path("hello") - .and(warp::path("world")) - .and(warp::path::param()) - .map(|name: String| format!("Hello, {}!", name)) +type Handlebars<'a> = sync::Arc>; + +fn render<'a, T: 'a>(hbs: Handlebars<'a>, name: &'a str, value: &'a T) -> impl warp::Reply +where + T: Serialize, +{ + // TODO deal with 404 + let render = hbs + .render(name, value) + .unwrap_or_else(|err| err.to_string()); + + let content_type = mime_guess::from_path(name) + .first_or_octet_stream() + .to_string(); + + warp::reply::with_header(warp::reply::html(render), "Content-Type", content_type) +} + +#[derive(Deserialize)] +struct DomainInitRequest { + //config: domain::config::Config, +} + +pub fn new( + _manager: impl domain::manager::Manager, +) -> Result< + impl warp::Filter + Clone, + Box, +> { + let hbs = sync::Arc::new(self::http_tpl::get()?); + let with_hbs = warp::any().map(move || hbs.clone()); + + let index = warp::get() + .and(warp::path::end()) + .and(with_hbs.clone()) + .map(|hbs: Handlebars<'_>| render(hbs, "/index.html", &())); + + let static_dir = warp::get() + .and(warp::path("static")) + .and(warp::path::full()) + .and(with_hbs.clone()) + .map(|full: warp::path::FullPath, hbs: Handlebars<'_>| render(hbs, full.as_str(), &())); + + //filter = warp::path!("domain" / "init").and(warp::post()) + // .and(warp::query::()) + // .map(|q: DomainInitRequest| { + // let config_hash = q.config.hash().expect("TODO"); + // warp::reply::html(config_hash) + // }); + + Ok(index.or(static_dir)) } diff --git a/src/service/http_tpl.rs b/src/service/http_tpl.rs new file mode 100644 index 0000000..8aac6e4 --- /dev/null +++ b/src/service/http_tpl.rs @@ -0,0 +1,12 @@ +use handlebars::{Handlebars, TemplateError}; + +#[derive(rust_embed::RustEmbed)] +#[folder = "src/service/http_tpl"] +#[prefix = "/"] +struct Dir; + +pub fn get() -> Result, TemplateError> { + let mut reg = Handlebars::new(); + reg.register_embed_templates::()?; + Ok(reg) +} diff --git a/src/service/http_tpl/index.html b/src/service/http_tpl/index.html new file mode 100644 index 0000000..cfc58e2 --- /dev/null +++ b/src/service/http_tpl/index.html @@ -0,0 +1,29 @@ + + + + + + + + + Hello, world! + + + + + + + + + + + +

OK

+ + + + + diff --git a/src/service/http_tpl/static/new.css b/src/service/http_tpl/static/new.css new file mode 100644 index 0000000..d253661 --- /dev/null +++ b/src/service/http_tpl/static/new.css @@ -0,0 +1,432 @@ +:root { + --nc-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --nc-font-mono: Consolas, monaco, 'Ubuntu Mono', 'Liberation Mono', 'Courier New', Courier, monospace; + --nc-tx-1: #000000; + --nc-tx-2: #1A1A1A; + --nc-bg-1: #FFFFFF; + --nc-bg-2: #F6F8FA; + --nc-bg-3: #E5E7EB; + --nc-lk-1: #0070F3; + --nc-lk-2: #0366D6; + --nc-lk-tx: #FFFFFF; + --nc-ac-1: #79FFE1; + --nc-ac-tx: #0C4047; +} + +@media (prefers-color-scheme: dark) { + :root { + --nc-tx-1: #ffffff; + --nc-tx-2: #eeeeee; + --nc-bg-1: #000000; + --nc-bg-2: #111111; + --nc-bg-3: #222222; + --nc-lk-1: #3291FF; + --nc-lk-2: #0070F3; + --nc-lk-tx: #FFFFFF; + --nc-ac-1: #7928CA; + --nc-ac-tx: #FFFFFF; + } +} + +* { + /* Reset margins and padding */ + margin: 0; + padding: 0; +} + +address, +area, +article, +aside, +audio, +blockquote, +datalist, +details, +dl, +fieldset, +figure, +form, +input, +iframe, +img, +meter, +nav, +ol, +optgroup, +option, +output, +p, +pre, +progress, +ruby, +section, +table, +textarea, +ul, +video { + /* Margins for most elements */ + margin-bottom: 1rem; +} + +html,input,select,button { + /* Set body font family and some finicky elements */ + font-family: var(--nc-font-sans); +} + +body { + /* Center body in page */ + margin: 0 auto; + max-width: 750px; + padding: 2rem; + border-radius: 6px; + overflow-x: hidden; + word-break: break-word; + overflow-wrap: break-word; + background: var(--nc-bg-1); + + /* Main body text */ + color: var(--nc-tx-2); + font-size: 1.03rem; + line-height: 1.5; +} + +::selection { + /* Set background color for selected text */ + background: var(--nc-ac-1); + color: var(--nc-ac-tx); +} + +h1,h2,h3,h4,h5,h6 { + line-height: 1; + color: var(--nc-tx-1); + padding-top: .875rem; +} + +h1, +h2, +h3 { + color: var(--nc-tx-1); + padding-bottom: 2px; + margin-bottom: 8px; + border-bottom: 1px solid var(--nc-bg-2); +} + +h4, +h5, +h6 { + margin-bottom: .3rem; +} + +h1 { + font-size: 2.25rem; +} + +h2 { + font-size: 1.85rem; +} + +h3 { + font-size: 1.55rem; +} + +h4 { + font-size: 1.25rem; +} + +h5 { + font-size: 1rem; +} + +h6 { + font-size: .875rem; +} + +a { + color: var(--nc-lk-1); +} + +a:hover { + color: var(--nc-lk-2); +} + +abbr:hover { + /* Set the '?' cursor while hovering an abbreviation */ + cursor: help; +} + +blockquote { + padding: 1.5rem; + background: var(--nc-bg-2); + border-left: 5px solid var(--nc-bg-3); +} + +abbr { + cursor: help; +} + +blockquote *:last-child { + padding-bottom: 0; + margin-bottom: 0; +} + +header { + background: var(--nc-bg-2); + border-bottom: 1px solid var(--nc-bg-3); + padding: 2rem 1.5rem; + + /* This sets the right and left margins to cancel out the body's margins. It's width is still the same, but the background stretches across the page's width. */ + + margin: -2rem calc(0px - (50vw - 50%)) 2rem; + + /* Shorthand for: + + margin-top: -2rem; + margin-bottom: 2rem; + + margin-left: calc(0px - (50vw - 50%)); + margin-right: calc(0px - (50vw - 50%)); */ + + padding-left: calc(50vw - 50%); + padding-right: calc(50vw - 50%); +} + +header h1, +header h2, +header h3 { + padding-bottom: 0; + border-bottom: 0; +} + +header > *:first-child { + margin-top: 0; + padding-top: 0; +} + +header > *:last-child { + margin-bottom: 0; +} + +a button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + font-size: 1rem; + display: inline-block; + padding: 6px 12px; + text-align: center; + text-decoration: none; + white-space: nowrap; + background: var(--nc-lk-1); + color: var(--nc-lk-tx); + border: 0; + border-radius: 4px; + box-sizing: border-box; + cursor: pointer; + color: var(--nc-lk-tx); +} + +a button[disabled], +button[disabled], +input[type="submit"][disabled], +input[type="reset"][disabled], +input[type="button"][disabled] { + cursor: default; + opacity: .5; + + /* Set the [X] cursor while hovering a disabled link */ + cursor: not-allowed; +} + +.button:focus, +.button:hover, +button:focus, +button:hover, +input[type="submit"]:focus, +input[type="submit"]:hover, +input[type="reset"]:focus, +input[type="reset"]:hover, +input[type="button"]:focus, +input[type="button"]:hover { + background: var(--nc-lk-2); +} + +code, +pre, +kbd, +samp { + /* Set the font family for monospaced elements */ + font-family: var(--nc-font-mono); +} + +code, +samp, +kbd, +pre { + /* The main preformatted style. This is changed slightly across different cases. */ + background: var(--nc-bg-2); + border: 1px solid var(--nc-bg-3); + border-radius: 4px; + padding: 3px 6px; + font-size: 0.9rem; +} + +kbd { + /* Makes the kbd element look like a keyboard key */ + border-bottom: 3px solid var(--nc-bg-3); +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; +} + +pre code { + /* When is in a
, reset it's formatting to blend in */
+	background: inherit;
+	font-size: inherit;
+	color: inherit;
+	border: 0;
+	padding: 0;
+	margin: 0;
+}
+
+code pre {
+	/* When 
 is in a , reset it's formatting to blend in */
+	display: inline;
+	background: inherit;
+	font-size: inherit;
+	color: inherit;
+	border: 0;
+	padding: 0;
+	margin: 0;
+}
+
+details {
+	/* Make the 
look more "clickable" */ + padding: .6rem 1rem; + background: var(--nc-bg-2); + border: 1px solid var(--nc-bg-3); + border-radius: 4px; +} + +summary { + /* Makes the look more like a "clickable" link with the pointer cursor */ + cursor: pointer; + font-weight: bold; +} + +details[open] { + /* Adjust the
padding while open */ + padding-bottom: .75rem; +} + +details[open] summary { + /* Adjust the
padding while open */ + margin-bottom: 6px; +} + +details[open]>*:last-child { + /* Resets the bottom margin of the last element in the
while
is opened. This prevents double margins/paddings. */ + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd::before { + /* Add an arrow to data table definitions */ + content: '→ '; +} + +hr { + /* Reset the border of the
separator, then set a better line */ + border: 0; + border-bottom: 1px solid var(--nc-bg-3); + margin: 1rem auto; +} + +fieldset { + margin-top: 1rem; + padding: 2rem; + border: 1px solid var(--nc-bg-3); + border-radius: 4px; +} + +legend { + padding: auto .5rem; +} + +table { + /* border-collapse sets the table's elements to share borders, rather than floating as separate "boxes". */ + border-collapse: collapse; + width: 100% +} + +td, +th { + border: 1px solid var(--nc-bg-3); + text-align: left; + padding: .5rem; +} + +th { + background: var(--nc-bg-2); +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background: var(--nc-bg-2); +} + +table caption { + font-weight: bold; + margin-bottom: .5rem; +} + +textarea { + /* Don't let the