From 5d05ceb1666edb7d7dc303c821e32206dfca8079 Mon Sep 17 00:00:00 2001 From: Dominik Werder Date: Wed, 13 Dec 2023 14:56:54 +0100 Subject: [PATCH] Include api docs as compressed book in build --- .gitignore | 2 + apidoc/book.toml | 6 ++ apidoc/src/SUMMARY.md | 6 ++ apidoc/src/bins.md | 1 + apidoc/src/events.md | 1 + apidoc/src/intro.md | 1 + apidoc/src/search.md | 1 + crates/httpret/Cargo.toml | 3 + crates/httpret/src/api4.rs | 1 + crates/httpret/src/api4/docs.rs | 121 ++++++++++++++++++++++++++++++++ crates/httpret/src/httpret.rs | 71 +------------------ crates/httpret/src/proxy.rs | 17 +---- 12 files changed, 147 insertions(+), 84 deletions(-) create mode 100644 apidoc/book.toml create mode 100644 apidoc/src/SUMMARY.md create mode 100644 apidoc/src/bins.md create mode 100644 apidoc/src/events.md create mode 100644 apidoc/src/intro.md create mode 100644 apidoc/src/search.md create mode 100644 crates/httpret/src/api4/docs.rs diff --git a/.gitignore b/.gitignore index 7611f47..4402507 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ /docs /docs1 /docs2 +/apidoc/book +/apidoc/book.cbor diff --git a/apidoc/book.toml b/apidoc/book.toml new file mode 100644 index 0000000..97786f0 --- /dev/null +++ b/apidoc/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Dominik Werder "] +language = "en" +multilingual = false +src = "src" +title = "Daqbuf API" diff --git a/apidoc/src/SUMMARY.md b/apidoc/src/SUMMARY.md new file mode 100644 index 0000000..369249a --- /dev/null +++ b/apidoc/src/SUMMARY.md @@ -0,0 +1,6 @@ +# Summary + +[Introduction](intro.md) +- [Search Channels](search.md) +- [Binned Data](bins.md) +- [Event Data](events.md) diff --git a/apidoc/src/bins.md b/apidoc/src/bins.md new file mode 100644 index 0000000..0bfce0a --- /dev/null +++ b/apidoc/src/bins.md @@ -0,0 +1 @@ +# Binned Data diff --git a/apidoc/src/events.md b/apidoc/src/events.md new file mode 100644 index 0000000..5d84dea --- /dev/null +++ b/apidoc/src/events.md @@ -0,0 +1 @@ +# Event Data diff --git a/apidoc/src/intro.md b/apidoc/src/intro.md new file mode 100644 index 0000000..e10b99d --- /dev/null +++ b/apidoc/src/intro.md @@ -0,0 +1 @@ +# Introduction diff --git a/apidoc/src/search.md b/apidoc/src/search.md new file mode 100644 index 0000000..750e3a7 --- /dev/null +++ b/apidoc/src/search.md @@ -0,0 +1 @@ +# Search Channels diff --git a/crates/httpret/Cargo.toml b/crates/httpret/Cargo.toml index bf664a7..864f92c 100644 --- a/crates/httpret/Cargo.toml +++ b/crates/httpret/Cargo.toml @@ -25,6 +25,9 @@ chrono = "0.4.23" md-5 = "0.10.6" regex = "1.10.2" rand = "0.8.5" +ciborium = "0.2.1" +flate2 = "1" +brotli = "2.4" err = { path = "../err" } netpod = { path = "../netpod" } query = { path = "../query" } diff --git a/crates/httpret/src/api4.rs b/crates/httpret/src/api4.rs index 69c9b8d..65683ac 100644 --- a/crates/httpret/src/api4.rs +++ b/crates/httpret/src/api4.rs @@ -1,5 +1,6 @@ pub mod binned; pub mod databuffer_tools; +pub mod docs; pub mod eventdata; pub mod events; pub mod maintenance; diff --git a/crates/httpret/src/api4/docs.rs b/crates/httpret/src/api4/docs.rs new file mode 100644 index 0000000..d01d471 --- /dev/null +++ b/crates/httpret/src/api4/docs.rs @@ -0,0 +1,121 @@ +use crate::bodystream::response; +use crate::err::Error; +use bytes::Bytes; +use http::StatusCode; +use httpclient::body_bytes; +use httpclient::body_empty; +use httpclient::Requ; +use httpclient::StreamResponse; +use netpod::log::*; +use netpod::ReqCtx; +use serde::Deserialize; +use serde::Serialize; +use std::collections::HashMap; +use std::collections::VecDeque; +use std::io::Cursor; +use std::io::Write; +use std::sync::OnceLock; +use std::time::Instant; + +#[derive(Debug, Serialize, Deserialize)] +struct Contents { + files: HashMap>, + dirs: HashMap, +} + +impl Contents { + fn new() -> Self { + Self { + files: HashMap::new(), + dirs: HashMap::new(), + } + } +} + +fn all_files() -> &'static Contents { + static CELL: OnceLock = OnceLock::new(); + CELL.get_or_init(extract_all_files) +} + +fn extract_all_files() -> Contents { + let ts1 = Instant::now(); + let buf1 = Vec::with_capacity(1024 * 1024 * 12); + let mut dec = brotli::DecompressorWriter::new(buf1, 1024 * 12); + // let mut dec = flate2::write::ZlibDecoder::new(buf1); + if let Err(e) = dec.write_all(blob()) { + error!("can not decode {e}"); + return Contents::new(); + } + if let Err(e) = dec.flush() { + error!("can not decode {e}"); + return Contents::new(); + } + let buf1 = dec.get_ref(); + // let buf1 = match dec.finish() { + // Ok(x) => x, + // Err(e) => { + // error!("can not decode {e}"); + // return Contents::new(); + // } + // }; + match ciborium::from_reader::(Cursor::new(buf1)) { + Ok(x) => { + let ts2 = Instant::now(); + let dt = ts2.saturating_duration_since(ts1); + debug!("content br blob loaded in {:.0} ms", 1e3 * dt.as_secs_f32()); + x + } + Err(_) => { + error!("can not read apidoc"); + Contents::new() + } + } +} + +fn blob() -> &'static [u8] { + include_bytes!(concat!("../../../../apidoc/book.cbor")) +} + +pub struct DocsHandler {} + +impl DocsHandler { + pub fn path_prefix() -> &'static str { + "/api/4/docs/" + } + + pub fn handler(req: &Requ) -> Option { + if req.uri().path().starts_with(Self::path_prefix()) { + Some(Self {}) + } else { + None + } + } + + pub async fn handle(&self, req: Requ, _ctx: &ReqCtx) -> Result { + let path = req.uri().path(); + let mut segs: VecDeque<_> = path.split("/").collect(); + for _ in 0..4 { + segs.pop_front(); + } + let mut data = all_files(); + loop { + if let Some(seg) = segs.pop_front() { + if segs.len() == 0 { + if let Some(x) = data.files.get(seg) { + return Ok(response(StatusCode::OK).body(body_bytes(Bytes::from(x.as_slice())))?); + } else { + return Ok(response(StatusCode::NOT_FOUND).body(body_empty())?); + } + } else { + if let Some(x) = data.dirs.get(seg) { + data = x; + } else { + return Ok(response(StatusCode::NOT_FOUND).body(body_empty())?); + } + } + } else { + return Ok(response(StatusCode::NOT_FOUND).body(body_empty())?); + } + } + } +} diff --git a/crates/httpret/src/httpret.rs b/crates/httpret/src/httpret.rs index d2b5ed6..a892bea 100644 --- a/crates/httpret/src/httpret.rs +++ b/crates/httpret/src/httpret.rs @@ -241,48 +241,6 @@ where Ok(ret) } -macro_rules! static_http { - ($path:expr, $tgt:expr, $tgtex:expr, $ctype:expr) => { - if $path == concat!("/api/4/documentation/", $tgt) { - let c = include_bytes!(concat!("../static/documentation/", $tgtex)); - let ret = response(StatusCode::OK) - .header("content-type", $ctype) - .body(body_bytes(c.to_vec()))?; - return Ok(ret); - } - }; - ($path:expr, $tgt:expr, $ctype:expr) => { - if $path == concat!("/api/4/documentation/", $tgt) { - let c = include_bytes!(concat!("../static/documentation/", $tgt)); - let ret = response(StatusCode::OK) - .header("content-type", $ctype) - .body(body_bytes(c.to_vec()))?; - return Ok(ret); - } - }; -} - -macro_rules! static_http_api1 { - ($path:expr, $tgt:expr, $tgtex:expr, $ctype:expr) => { - if $path == concat!("/api/1/documentation/", $tgt) { - let c = include_bytes!(concat!("../static/documentation/", $tgtex)); - let ret = response(StatusCode::OK) - .header("content-type", $ctype) - .body(body_bytes(c.to_vec()))?; - return Ok(ret); - } - }; - ($path:expr, $tgt:expr, $ctype:expr) => { - if $path == concat!("/api/1/documentation/", $tgt) { - let c = include_bytes!(concat!("../static/documentation/", $tgt)); - let ret = response(StatusCode::OK) - .header("content-type", $ctype) - .body(body_bytes(c.to_vec()))?; - return Ok(ret); - } - }; -} - async fn http_service_try( req: Requ, ctx: ReqCtx, @@ -449,18 +407,8 @@ async fn http_service_inner( Ok(h.handle(req, &node_config).await?) } else if let Some(h) = api1::RequestStatusHandler::handler(&req) { Ok(h.handle(req, &node_config).await?) - } else if path.starts_with("/api/1/documentation/") { - if req.method() == Method::GET { - api_1_docs(path) - } else { - Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(body_empty())?) - } - } else if path.starts_with("/api/4/documentation/") { - if req.method() == Method::GET { - api_4_docs(path) - } else { - Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(body_empty())?) - } + } else if let Some(h) = api4::docs::DocsHandler::handler(&req) { + Ok(h.handle(req, ctx).await?) } else { use std::fmt::Write; let mut body = String::new(); @@ -480,21 +428,6 @@ async fn http_service_inner( } } -pub fn api_4_docs(path: &str) -> Result { - static_http!(path, "", "api4.html", "text/html"); - static_http!(path, "style.css", "text/css"); - static_http!(path, "script.js", "text/javascript"); - static_http!(path, "status-main.html", "text/html"); - Ok(response(StatusCode::NOT_FOUND).body(body_empty())?) -} - -pub fn api_1_docs(path: &str) -> Result { - static_http_api1!(path, "", "api1.html", "text/html"); - static_http_api1!(path, "style.css", "text/css"); - static_http_api1!(path, "script.js", "text/javascript"); - Ok(response(StatusCode::NOT_FOUND).body(body_empty())?) -} - pub struct StatusBoardAllHandler {} impl StatusBoardAllHandler { diff --git a/crates/httpret/src/proxy.rs b/crates/httpret/src/proxy.rs index d76fdaa..7a8e066 100644 --- a/crates/httpret/src/proxy.rs +++ b/crates/httpret/src/proxy.rs @@ -4,8 +4,6 @@ pub mod api4; use crate::api1::channel_search_configs_v1; use crate::api1::channel_search_list_v1; use crate::api1::gather_json_2_v1; -use crate::api_1_docs; -use crate::api_4_docs; use crate::err::Error; use crate::gather::gather_get_json_generic; use crate::gather::SubRes; @@ -48,7 +46,6 @@ use netpod::ACCEPT_ALL; use netpod::APP_JSON; use netpod::PSI_DAQBUFFER_SERVICE_MARK; use query::api4::binned::BinnedQuery; -use query::api4::events::PlainEventsQuery; use serde::Deserialize; use serde::Serialize; use serde_json::Value as JsonValue; @@ -204,18 +201,6 @@ async fn proxy_http_service_inner( Ok(proxy_single_backend_query::(req, ctx, proxy_config).await?) } else if path == "/api/4/channel/config" { Ok(proxy_single_backend_query::(req, ctx, proxy_config).await?) - } else if path.starts_with("/api/1/documentation/") { - if req.method() == Method::GET { - Ok(api_1_docs(path)?) - } else { - Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(body_empty())?) - } - } else if path.starts_with("/api/4/documentation/") { - if req.method() == Method::GET { - Ok(api_4_docs(path)?) - } else { - Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(body_empty())?) - } } else if path.starts_with("/api/4/test/http/204") { Ok(response(StatusCode::NO_CONTENT).body(body_string("No Content"))?) } else if path.starts_with("/api/4/test/http/400") { @@ -242,6 +227,8 @@ async fn proxy_http_service_inner( h.handle(req, proxy_config).await } else if path.starts_with(DISTRI_PRE) { proxy_distribute_v2(req).await + } else if let Some(h) = super::api4::docs::DocsHandler::handler(&req) { + Ok(h.handle(req, ctx).await?) } else { use std::fmt::Write; let mut body = String::new();