Include api docs as compressed book in build

This commit is contained in:
Dominik Werder
2023-12-13 14:56:54 +01:00
parent 45421415d0
commit 5d05ceb166
12 changed files with 147 additions and 84 deletions

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@
/docs
/docs1
/docs2
/apidoc/book
/apidoc/book.cbor

6
apidoc/book.toml Normal file
View File

@@ -0,0 +1,6 @@
[book]
authors = ["Dominik Werder <dominik.werder@gmail.com>"]
language = "en"
multilingual = false
src = "src"
title = "Daqbuf API"

6
apidoc/src/SUMMARY.md Normal file
View File

@@ -0,0 +1,6 @@
# Summary
[Introduction](intro.md)
- [Search Channels](search.md)
- [Binned Data](bins.md)
- [Event Data](events.md)

1
apidoc/src/bins.md Normal file
View File

@@ -0,0 +1 @@
# Binned Data

1
apidoc/src/events.md Normal file
View File

@@ -0,0 +1 @@
# Event Data

1
apidoc/src/intro.md Normal file
View File

@@ -0,0 +1 @@
# Introduction

1
apidoc/src/search.md Normal file
View File

@@ -0,0 +1 @@
# Search Channels

View File

@@ -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" }

View File

@@ -1,5 +1,6 @@
pub mod binned;
pub mod databuffer_tools;
pub mod docs;
pub mod eventdata;
pub mod events;
pub mod maintenance;

View File

@@ -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<String, Vec<u8>>,
dirs: HashMap<String, Contents>,
}
impl Contents {
fn new() -> Self {
Self {
files: HashMap::new(),
dirs: HashMap::new(),
}
}
}
fn all_files() -> &'static Contents {
static CELL: OnceLock<Contents> = 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::<Contents, _>(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<Self> {
if req.uri().path().starts_with(Self::path_prefix()) {
Some(Self {})
} else {
None
}
}
pub async fn handle(&self, req: Requ, _ctx: &ReqCtx) -> Result<StreamResponse, Error> {
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())?);
}
}
}
}

View File

@@ -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<StreamResponse, RetrievalError> {
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<StreamResponse, RetrievalError> {
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 {

View File

@@ -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::<BinnedQuery>(req, ctx, proxy_config).await?)
} else if path == "/api/4/channel/config" {
Ok(proxy_single_backend_query::<ChannelConfigQuery>(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();