Include api docs as compressed book in build
This commit is contained in:
@@ -8,3 +8,5 @@
|
|||||||
/docs
|
/docs
|
||||||
/docs1
|
/docs1
|
||||||
/docs2
|
/docs2
|
||||||
|
/apidoc/book
|
||||||
|
/apidoc/book.cbor
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[book]
|
||||||
|
authors = ["Dominik Werder <dominik.werder@gmail.com>"]
|
||||||
|
language = "en"
|
||||||
|
multilingual = false
|
||||||
|
src = "src"
|
||||||
|
title = "Daqbuf API"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
[Introduction](intro.md)
|
||||||
|
- [Search Channels](search.md)
|
||||||
|
- [Binned Data](bins.md)
|
||||||
|
- [Event Data](events.md)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Binned Data
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Event Data
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Introduction
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Search Channels
|
||||||
@@ -25,6 +25,9 @@ chrono = "0.4.23"
|
|||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
ciborium = "0.2.1"
|
||||||
|
flate2 = "1"
|
||||||
|
brotli = "2.4"
|
||||||
err = { path = "../err" }
|
err = { path = "../err" }
|
||||||
netpod = { path = "../netpod" }
|
netpod = { path = "../netpod" }
|
||||||
query = { path = "../query" }
|
query = { path = "../query" }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod binned;
|
pub mod binned;
|
||||||
pub mod databuffer_tools;
|
pub mod databuffer_tools;
|
||||||
|
pub mod docs;
|
||||||
pub mod eventdata;
|
pub mod eventdata;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod maintenance;
|
pub mod maintenance;
|
||||||
|
|||||||
@@ -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())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -241,48 +241,6 @@ where
|
|||||||
Ok(ret)
|
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(
|
async fn http_service_try(
|
||||||
req: Requ,
|
req: Requ,
|
||||||
ctx: ReqCtx,
|
ctx: ReqCtx,
|
||||||
@@ -449,18 +407,8 @@ async fn http_service_inner(
|
|||||||
Ok(h.handle(req, &node_config).await?)
|
Ok(h.handle(req, &node_config).await?)
|
||||||
} else if let Some(h) = api1::RequestStatusHandler::handler(&req) {
|
} else if let Some(h) = api1::RequestStatusHandler::handler(&req) {
|
||||||
Ok(h.handle(req, &node_config).await?)
|
Ok(h.handle(req, &node_config).await?)
|
||||||
} else if path.starts_with("/api/1/documentation/") {
|
} else if let Some(h) = api4::docs::DocsHandler::handler(&req) {
|
||||||
if req.method() == Method::GET {
|
Ok(h.handle(req, ctx).await?)
|
||||||
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 {
|
} else {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut body = String::new();
|
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 {}
|
pub struct StatusBoardAllHandler {}
|
||||||
|
|
||||||
impl StatusBoardAllHandler {
|
impl StatusBoardAllHandler {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ pub mod api4;
|
|||||||
use crate::api1::channel_search_configs_v1;
|
use crate::api1::channel_search_configs_v1;
|
||||||
use crate::api1::channel_search_list_v1;
|
use crate::api1::channel_search_list_v1;
|
||||||
use crate::api1::gather_json_2_v1;
|
use crate::api1::gather_json_2_v1;
|
||||||
use crate::api_1_docs;
|
|
||||||
use crate::api_4_docs;
|
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::gather::gather_get_json_generic;
|
use crate::gather::gather_get_json_generic;
|
||||||
use crate::gather::SubRes;
|
use crate::gather::SubRes;
|
||||||
@@ -48,7 +46,6 @@ use netpod::ACCEPT_ALL;
|
|||||||
use netpod::APP_JSON;
|
use netpod::APP_JSON;
|
||||||
use netpod::PSI_DAQBUFFER_SERVICE_MARK;
|
use netpod::PSI_DAQBUFFER_SERVICE_MARK;
|
||||||
use query::api4::binned::BinnedQuery;
|
use query::api4::binned::BinnedQuery;
|
||||||
use query::api4::events::PlainEventsQuery;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value as JsonValue;
|
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?)
|
Ok(proxy_single_backend_query::<BinnedQuery>(req, ctx, proxy_config).await?)
|
||||||
} else if path == "/api/4/channel/config" {
|
} else if path == "/api/4/channel/config" {
|
||||||
Ok(proxy_single_backend_query::<ChannelConfigQuery>(req, ctx, proxy_config).await?)
|
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") {
|
} else if path.starts_with("/api/4/test/http/204") {
|
||||||
Ok(response(StatusCode::NO_CONTENT).body(body_string("No Content"))?)
|
Ok(response(StatusCode::NO_CONTENT).body(body_string("No Content"))?)
|
||||||
} else if path.starts_with("/api/4/test/http/400") {
|
} 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
|
h.handle(req, proxy_config).await
|
||||||
} else if path.starts_with(DISTRI_PRE) {
|
} else if path.starts_with(DISTRI_PRE) {
|
||||||
proxy_distribute_v2(req).await
|
proxy_distribute_v2(req).await
|
||||||
|
} else if let Some(h) = super::api4::docs::DocsHandler::handler(&req) {
|
||||||
|
Ok(h.handle(req, ctx).await?)
|
||||||
} else {
|
} else {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut body = String::new();
|
let mut body = String::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user