Add json-framed encoding, docs, refactor
This commit is contained in:
@@ -20,6 +20,9 @@ use netpod::req_uri_to_url;
|
||||
use netpod::FromUrl;
|
||||
use netpod::NodeConfigCached;
|
||||
use query::api4::AccountingIngestedBytesQuery;
|
||||
use query::api4::AccountingToplistQuery;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub struct AccountingIngestedBytes {}
|
||||
|
||||
@@ -73,7 +76,7 @@ impl AccountingIngestedBytes {
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::with_public_msg_no_trace(format!("no scylla configured")))?;
|
||||
let scy = scyllaconn::conn::create_scy_session(scyco).await?;
|
||||
let mut stream = scyllaconn::accounting::AccountingStreamScylla::new(q.range().try_into()?, scy);
|
||||
let mut stream = scyllaconn::accounting::totals::AccountingStreamScylla::new(q.range().try_into()?, scy);
|
||||
let mut ret = AccountingEvents::empty();
|
||||
while let Some(item) = stream.next().await {
|
||||
let mut item = item?;
|
||||
@@ -82,3 +85,83 @@ impl AccountingIngestedBytes {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Toplist {
|
||||
toplist: Vec<(String, u64, u64)>,
|
||||
}
|
||||
|
||||
pub struct AccountingToplistCounts {}
|
||||
|
||||
impl AccountingToplistCounts {
|
||||
pub fn handler(req: &Requ) -> Option<Self> {
|
||||
if req.uri().path().starts_with("/api/4/accounting/toplist/counts") {
|
||||
Some(Self {})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle(&self, req: Requ, ctx: &ReqCtx, ncc: &NodeConfigCached) -> Result<StreamResponse, Error> {
|
||||
if req.method() == Method::GET {
|
||||
if accepts_json_or_all(req.headers()) {
|
||||
match self.handle_get(req, ctx, ncc).await {
|
||||
Ok(x) => Ok(x),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
let e2 = e.to_public_error();
|
||||
let s = serde_json::to_string(&e2)?;
|
||||
Ok(response(StatusCode::INTERNAL_SERVER_ERROR).body(body_string(s))?)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(response(StatusCode::BAD_REQUEST).body(body_empty())?)
|
||||
}
|
||||
} else {
|
||||
Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(body_empty())?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_get(&self, req: Requ, ctx: &ReqCtx, ncc: &NodeConfigCached) -> Result<StreamResponse, Error> {
|
||||
let url = req_uri_to_url(req.uri())?;
|
||||
let qu = AccountingToplistQuery::from_url(&url)?;
|
||||
let res = self.fetch_data(qu, ctx, ncc).await?;
|
||||
let body = ToJsonBody::from(&res).into_body();
|
||||
Ok(response(StatusCode::OK).body(body)?)
|
||||
}
|
||||
|
||||
async fn fetch_data(
|
||||
&self,
|
||||
qu: AccountingToplistQuery,
|
||||
_ctx: &ReqCtx,
|
||||
ncc: &NodeConfigCached,
|
||||
) -> Result<Toplist, Error> {
|
||||
let scyco = ncc
|
||||
.node_config
|
||||
.cluster
|
||||
.scylla
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::with_public_msg_no_trace(format!("no scylla configured")))?;
|
||||
let scy = scyllaconn::conn::create_scy_session(scyco).await?;
|
||||
let pgconf = &ncc.node_config.cluster.database;
|
||||
let pg = dbconn::create_connection(&pgconf).await?;
|
||||
let mut top1 = scyllaconn::accounting::toplist::read_ts(qu.ts().0, scy).await?;
|
||||
top1.sort_by_bytes();
|
||||
let mut ret = Toplist { toplist: Vec::new() };
|
||||
let series_ids: Vec<_> = top1.usage().iter().take(qu.limit() as _).map(|x| x.0).collect();
|
||||
let infos = dbconn::channelinfo::info_for_series_ids(&series_ids, &pg)
|
||||
.await
|
||||
.map_err(Error::from_to_string)?;
|
||||
let mut it = top1.usage().iter();
|
||||
for info in infos {
|
||||
let h = it.next().ok_or_else(|| Error::with_msg_no_trace("logic error"))?;
|
||||
if info.series != h.0 {
|
||||
let e = Error::with_msg_no_trace(format!("mismatch {} != {}", info.series, h.0));
|
||||
warn!("{e}");
|
||||
return Err(e);
|
||||
}
|
||||
ret.toplist.push((info.name, h.1, h.2));
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ fn extract_all_files() -> Contents {
|
||||
}
|
||||
}
|
||||
|
||||
// .
|
||||
fn blob() -> &'static [u8] {
|
||||
include_bytes!(concat!("../../../../apidoc/book.cbor"))
|
||||
}
|
||||
@@ -84,7 +85,7 @@ impl DocsHandler {
|
||||
}
|
||||
|
||||
pub fn handler(req: &Requ) -> Option<Self> {
|
||||
if req.uri().path().starts_with(Self::path_prefix()) {
|
||||
if req.uri().path().starts_with(Self::path_prefix()) || req.uri().path().starts_with("/api/4/documentation") {
|
||||
Some(Self {})
|
||||
} else {
|
||||
None
|
||||
@@ -93,6 +94,13 @@ impl DocsHandler {
|
||||
|
||||
pub async fn handle(&self, req: Requ, _ctx: &ReqCtx) -> Result<StreamResponse, Error> {
|
||||
let path = req.uri().path();
|
||||
if path.starts_with("/api/4/documentation") {
|
||||
let ret = http::Response::builder()
|
||||
.status(StatusCode::TEMPORARY_REDIRECT)
|
||||
.header(http::header::LOCATION, "/api/4/docs/")
|
||||
.body(body_empty())?;
|
||||
return Ok(ret);
|
||||
}
|
||||
if path == "/api/4/docs" {
|
||||
let ret = http::Response::builder()
|
||||
.status(StatusCode::TEMPORARY_REDIRECT)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::bodystream::response_err_msg;
|
||||
use crate::channelconfig::chconf_from_events_quorum;
|
||||
use crate::err::Error;
|
||||
use crate::requests::accepts_cbor_frames;
|
||||
use crate::requests::accepts_cbor_framed;
|
||||
use crate::requests::accepts_json_framed;
|
||||
use crate::requests::accepts_json_or_all;
|
||||
use crate::response;
|
||||
use crate::ToPublicResponse;
|
||||
@@ -9,6 +10,7 @@ use bytes::Bytes;
|
||||
use bytes::BytesMut;
|
||||
use futures_util::future;
|
||||
use futures_util::stream;
|
||||
use futures_util::Stream;
|
||||
use futures_util::StreamExt;
|
||||
use http::Method;
|
||||
use http::StatusCode;
|
||||
@@ -59,8 +61,10 @@ impl EventsHandler {
|
||||
|
||||
async fn plain_events(req: Requ, ctx: &ReqCtx, node_config: &NodeConfigCached) -> Result<StreamResponse, Error> {
|
||||
let url = req_uri_to_url(req.uri())?;
|
||||
if accepts_cbor_frames(req.headers()) {
|
||||
Ok(plain_events_cbor(url, req, ctx, node_config).await?)
|
||||
if accepts_cbor_framed(req.headers()) {
|
||||
Ok(plain_events_cbor_framed(url, req, ctx, node_config).await?)
|
||||
} else if accepts_json_framed(req.headers()) {
|
||||
Ok(plain_events_json_framed(url, req, ctx, node_config).await?)
|
||||
} else if accepts_json_or_all(req.headers()) {
|
||||
Ok(plain_events_json(url, req, ctx, node_config).await?)
|
||||
} else {
|
||||
@@ -69,32 +73,62 @@ async fn plain_events(req: Requ, ctx: &ReqCtx, node_config: &NodeConfigCached) -
|
||||
}
|
||||
}
|
||||
|
||||
async fn plain_events_cbor(url: Url, req: Requ, ctx: &ReqCtx, ncc: &NodeConfigCached) -> Result<StreamResponse, Error> {
|
||||
async fn plain_events_cbor_framed(
|
||||
url: Url,
|
||||
req: Requ,
|
||||
ctx: &ReqCtx,
|
||||
ncc: &NodeConfigCached,
|
||||
) -> Result<StreamResponse, Error> {
|
||||
let evq = PlainEventsQuery::from_url(&url).map_err(|e| e.add_public_msg(format!("Can not understand query")))?;
|
||||
let ch_conf = chconf_from_events_quorum(&evq, ctx, ncc)
|
||||
.await?
|
||||
.ok_or_else(|| Error::with_msg_no_trace("channel not found"))?;
|
||||
info!("plain_events_cbor chconf_from_events_quorum: {ch_conf:?} {req:?}");
|
||||
info!("plain_events_cbor_framed chconf_from_events_quorum: {ch_conf:?} {req:?}");
|
||||
let open_bytes = OpenBoxedBytesViaHttp::new(ncc.node_config.cluster.clone());
|
||||
let stream = streams::plaineventscbor::plain_events_cbor(&evq, ch_conf, ctx, Box::pin(open_bytes)).await?;
|
||||
let stream = streams::plaineventscbor::plain_events_cbor_stream(&evq, ch_conf, ctx, Box::pin(open_bytes)).await?;
|
||||
use future::ready;
|
||||
let stream = stream
|
||||
.flat_map(|x| match x {
|
||||
Ok(y) => {
|
||||
use bytes::BufMut;
|
||||
let buf = y.into_inner();
|
||||
let mut b2 = BytesMut::with_capacity(8);
|
||||
let adv = (buf.len() + 7) / 8 * 8;
|
||||
let pad = adv - buf.len();
|
||||
let mut b2 = BytesMut::with_capacity(16);
|
||||
b2.put_u32_le(buf.len() as u32);
|
||||
stream::iter([Ok::<_, Error>(b2.freeze()), Ok(buf)])
|
||||
b2.put_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
let mut b3 = BytesMut::with_capacity(16);
|
||||
b3.put_slice(&[0, 0, 0, 0, 0, 0, 0, 0][..pad]);
|
||||
stream::iter([Ok::<_, Error>(b2.freeze()), Ok(buf), Ok(b3.freeze())])
|
||||
}
|
||||
Err(e) => {
|
||||
let e = Error::with_msg_no_trace(e.to_string());
|
||||
stream::iter([Err(e), Ok(Bytes::new()), Ok(Bytes::new())])
|
||||
}
|
||||
// TODO handle other cases
|
||||
_ => stream::iter([Ok(Bytes::new()), Ok(Bytes::new())]),
|
||||
})
|
||||
.filter(|x| if let Ok(x) = x { ready(x.len() > 0) } else { ready(true) });
|
||||
let ret = response(StatusCode::OK).body(body_stream(stream))?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
async fn plain_events_json_framed(
|
||||
url: Url,
|
||||
req: Requ,
|
||||
ctx: &ReqCtx,
|
||||
ncc: &NodeConfigCached,
|
||||
) -> Result<StreamResponse, Error> {
|
||||
let evq = PlainEventsQuery::from_url(&url).map_err(|e| e.add_public_msg(format!("Can not understand query")))?;
|
||||
let ch_conf = chconf_from_events_quorum(&evq, ctx, ncc)
|
||||
.await?
|
||||
.ok_or_else(|| Error::with_msg_no_trace("channel not found"))?;
|
||||
info!("plain_events_json_framed chconf_from_events_quorum: {ch_conf:?} {req:?}");
|
||||
let open_bytes = OpenBoxedBytesViaHttp::new(ncc.node_config.cluster.clone());
|
||||
let stream = streams::plaineventsjson::plain_events_json_stream(&evq, ch_conf, ctx, Box::pin(open_bytes)).await?;
|
||||
let stream = bytes_chunks_to_framed(stream);
|
||||
let ret = response(StatusCode::OK).body(body_stream(stream))?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
async fn plain_events_json(
|
||||
url: Url,
|
||||
req: Requ,
|
||||
@@ -133,3 +167,32 @@ async fn plain_events_json(
|
||||
info!("{self_name} response created");
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn bytes_chunks_to_framed<S, T>(stream: S) -> impl Stream<Item = Result<Bytes, Error>>
|
||||
where
|
||||
S: Stream<Item = Result<T, err::Error>>,
|
||||
T: Into<Bytes>,
|
||||
{
|
||||
use future::ready;
|
||||
stream
|
||||
// TODO unify this map to padded bytes for both json and cbor output
|
||||
.flat_map(|x| match x {
|
||||
Ok(y) => {
|
||||
use bytes::BufMut;
|
||||
let buf = y.into();
|
||||
let adv = (buf.len() + 7) / 8 * 8;
|
||||
let pad = adv - buf.len();
|
||||
let mut b2 = BytesMut::with_capacity(16);
|
||||
b2.put_u32_le(buf.len() as u32);
|
||||
b2.put_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
let mut b3 = BytesMut::with_capacity(16);
|
||||
b3.put_slice(&[0, 0, 0, 0, 0, 0, 0, 0][..pad]);
|
||||
stream::iter([Ok::<_, Error>(b2.freeze()), Ok(buf), Ok(b3.freeze())])
|
||||
}
|
||||
Err(e) => {
|
||||
let e = Error::with_msg_no_trace(e.to_string());
|
||||
stream::iter([Err(e), Ok(Bytes::new()), Ok(Bytes::new())])
|
||||
}
|
||||
})
|
||||
.filter(|x| if let Ok(x) = x { ready(x.len() > 0) } else { ready(true) })
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ pub async fn host(node_config: NodeConfigCached, service_version: ServiceVersion
|
||||
use std::str::FromStr;
|
||||
let bind_addr = SocketAddr::from_str(&format!("{}:{}", node_config.node.listen(), node_config.node.port))?;
|
||||
|
||||
// tokio::net::TcpSocket::new_v4()?.listen(200)?
|
||||
let listener = TcpListener::bind(bind_addr).await?;
|
||||
loop {
|
||||
let (stream, addr) = if let Ok(x) = listener.accept().await {
|
||||
@@ -323,6 +324,8 @@ async fn http_service_inner(
|
||||
Ok(h.handle(req, &node_config).await?)
|
||||
} else if let Some(h) = channelconfig::AmbigiousChannelNames::handler(&req) {
|
||||
Ok(h.handle(req, &node_config).await?)
|
||||
} else if let Some(h) = api4::accounting::AccountingToplistCounts::handler(&req) {
|
||||
Ok(h.handle(req, ctx, &node_config).await?)
|
||||
} else if let Some(h) = api4::accounting::AccountingIngestedBytes::handler(&req) {
|
||||
Ok(h.handle(req, ctx, &node_config).await?)
|
||||
} else if path == "/api/4/prebinned" {
|
||||
|
||||
@@ -190,6 +190,8 @@ async fn proxy_http_service_inner(
|
||||
h.handle(req, ctx, &proxy_config, service_version).await
|
||||
} else if path == "/api/4/backends" {
|
||||
Ok(backends(req, proxy_config).await?)
|
||||
} else if let Some(h) = api4::backend::BackendListHandler::handler(&req) {
|
||||
h.handle(req, ctx, &proxy_config).await
|
||||
} else if let Some(h) = api4::ChannelSearchAggHandler::handler(&req) {
|
||||
h.handle(req, ctx, &proxy_config).await
|
||||
} else if let Some(h) = api4::events::EventsHandler::handler(&req) {
|
||||
@@ -499,19 +501,31 @@ pub async fn proxy_backend_query<QT>(
|
||||
where
|
||||
QT: fmt::Debug + FromUrl + AppendToUrl + HasBackend + HasTimeout,
|
||||
{
|
||||
let (head, _body) = req.into_parts();
|
||||
// TODO will we need some mechanism to modify the necessary url?
|
||||
let url = req_uri_to_url(&head.uri)?;
|
||||
let query = match QT::from_url(&url) {
|
||||
let url = req_uri_to_url(req.uri())?;
|
||||
let mut query = match QT::from_url(&url) {
|
||||
Ok(k) => k,
|
||||
Err(_) => {
|
||||
let msg = format!("malformed request or missing parameters {head:?}");
|
||||
let msg = format!("malformed request or missing parameters {:?}", req.uri());
|
||||
warn!("{msg}");
|
||||
return Ok(response_err_msg(StatusCode::BAD_REQUEST, msg)?);
|
||||
}
|
||||
};
|
||||
debug!("proxy_backend_query {query:?} {head:?}");
|
||||
let query_host = get_query_host_for_backend(&query.backend(), proxy_config)?;
|
||||
debug!("proxy_backend_query {:?} {:?}", query, req.uri());
|
||||
let timeout = query.timeout();
|
||||
let timeout_next = timeout.saturating_sub(Duration::from_millis(1000));
|
||||
debug!("timeout {timeout:?} timeout_next {timeout_next:?}");
|
||||
query.set_timeout(timeout_next);
|
||||
let query = query;
|
||||
let backend = query.backend();
|
||||
let uri_path = proxy_backend_query_helper_uri_path(req.uri().path(), &url);
|
||||
debug!("uri_path {uri_path}");
|
||||
let query_host = get_query_host_for_backend(backend, proxy_config)?;
|
||||
let mut url = Url::parse(&format!("{}{}", query_host, uri_path))?;
|
||||
query.append_to_url(&mut url);
|
||||
proxy_backend_query_inner(req.headers(), url, timeout, ctx, proxy_config).await
|
||||
}
|
||||
|
||||
fn proxy_backend_query_helper_uri_path(path: &str, url: &Url) -> String {
|
||||
// TODO remove this special case
|
||||
// SPECIAL CASE:
|
||||
// Since the inner proxy is not yet handling map-pulse requests without backend,
|
||||
@@ -519,7 +533,7 @@ where
|
||||
// Instead, url needs to get parsed and formatted.
|
||||
// In general, the caller of this function should be able to provide a url, or maybe
|
||||
// better a closure so that the url can even depend on backend.
|
||||
let uri_path: String = if url.as_str().contains("/map/pulse/") {
|
||||
if url.as_str().contains("/map/pulse/") {
|
||||
match MapPulseQuery::from_url(&url) {
|
||||
Ok(qu) => {
|
||||
debug!("qu {qu:?}");
|
||||
@@ -531,26 +545,30 @@ where
|
||||
}
|
||||
}
|
||||
} else {
|
||||
head.uri.path().into()
|
||||
};
|
||||
debug!("uri_path {uri_path}");
|
||||
let mut url = Url::parse(&format!("{}{}", query_host, uri_path))?;
|
||||
query.append_to_url(&mut url);
|
||||
path.into()
|
||||
}
|
||||
}
|
||||
|
||||
let mut req = Request::builder()
|
||||
pub async fn proxy_backend_query_inner(
|
||||
headers: &http::HeaderMap,
|
||||
url: Url,
|
||||
timeout: Duration,
|
||||
ctx: &ReqCtx,
|
||||
_proxy_config: &ProxyConfig,
|
||||
) -> Result<StreamResponse, Error> {
|
||||
let host = url
|
||||
.host_str()
|
||||
.ok_or_else(|| Error::with_msg_no_trace("no host in url"))?;
|
||||
let mut req2 = Request::builder()
|
||||
.method(http::Method::GET)
|
||||
.uri(url.to_string())
|
||||
.header(
|
||||
header::HOST,
|
||||
url.host_str()
|
||||
.ok_or_else(|| Error::with_msg_no_trace("no host in url"))?,
|
||||
)
|
||||
.header(header::HOST, host)
|
||||
.header(X_DAQBUF_REQID, ctx.reqid());
|
||||
{
|
||||
let hs = req
|
||||
let hs = req2
|
||||
.headers_mut()
|
||||
.ok_or_else(|| Error::with_msg_no_trace("can not set headers"))?;
|
||||
for (hn, hv) in &head.headers {
|
||||
for (hn, hv) in headers {
|
||||
if hn == header::HOST {
|
||||
} else if hn == X_DAQBUF_REQID {
|
||||
} else {
|
||||
@@ -558,23 +576,27 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("send request {req:?}");
|
||||
let req = req.body(body_empty())?;
|
||||
|
||||
debug!("send request {req2:?}");
|
||||
let req2 = req2.body(body_empty())?;
|
||||
|
||||
let fut = async move {
|
||||
debug!("requesting {:?} {:?} {:?}", req.method(), req.uri(), req.headers());
|
||||
let mut send_req = httpclient::httpclient::connect_client(req.uri()).await?;
|
||||
let res = send_req.send_request(req).await?;
|
||||
debug!(
|
||||
"requesting {:?} {:?} {:?}",
|
||||
req2.method(),
|
||||
req2.uri(),
|
||||
req2.headers()
|
||||
);
|
||||
let mut send_req = httpclient::httpclient::connect_client(req2.uri()).await?;
|
||||
let res = send_req.send_request(req2).await?;
|
||||
Ok::<_, Error>(res)
|
||||
};
|
||||
|
||||
let res = tokio::time::timeout(Duration::from_millis(5000), fut)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
let e = Error::with_msg_no_trace(format!("timeout trying to make sub request"));
|
||||
warn!("{e}");
|
||||
e
|
||||
})??;
|
||||
let res = tokio::time::timeout(timeout, fut).await.map_err(|_| {
|
||||
let e = Error::with_msg_no_trace(format!("timeout trying to make sub request"));
|
||||
warn!("{e}");
|
||||
e
|
||||
})??;
|
||||
|
||||
{
|
||||
use bytes::Bytes;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod backend;
|
||||
pub mod caioclookup;
|
||||
pub mod events;
|
||||
|
||||
|
||||
39
crates/httpret/src/proxy/api4/backend.rs
Normal file
39
crates/httpret/src/proxy/api4/backend.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::bodystream::response;
|
||||
use crate::err::Error;
|
||||
use crate::requests::accepts_json_or_all;
|
||||
use http::Method;
|
||||
use http::StatusCode;
|
||||
use httpclient::body_empty;
|
||||
use httpclient::body_string;
|
||||
use httpclient::Requ;
|
||||
use httpclient::StreamResponse;
|
||||
use netpod::ProxyConfig;
|
||||
use netpod::ReqCtx;
|
||||
|
||||
pub struct BackendListHandler {}
|
||||
|
||||
impl BackendListHandler {
|
||||
pub fn handler(req: &Requ) -> Option<Self> {
|
||||
if req.uri().path() == "/api/4/backend/list" {
|
||||
Some(Self {})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle(&self, req: Requ, _ctx: &ReqCtx, _node_config: &ProxyConfig) -> Result<StreamResponse, Error> {
|
||||
if req.method() == Method::GET {
|
||||
if accepts_json_or_all(req.headers()) {
|
||||
let res = serde_json::json!({
|
||||
"backends_available": ["sf-databuffer"]
|
||||
});
|
||||
let body = serde_json::to_string(&res)?;
|
||||
Ok(response(StatusCode::OK).body(body_string(body))?)
|
||||
} else {
|
||||
Ok(response(StatusCode::BAD_REQUEST).body(body_empty())?)
|
||||
}
|
||||
} else {
|
||||
Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(body_empty())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::bodystream::response;
|
||||
use crate::bodystream::response_err_msg;
|
||||
use crate::err::Error;
|
||||
use crate::proxy::get_query_host_for_backend;
|
||||
use crate::requests::accepts_cbor_frames;
|
||||
use crate::requests::accepts_cbor_framed;
|
||||
use crate::requests::accepts_json_framed;
|
||||
use crate::requests::accepts_json_or_all;
|
||||
use crate::ReqCtx;
|
||||
use http::header;
|
||||
@@ -22,7 +22,8 @@ use netpod::req_uri_to_url;
|
||||
use netpod::FromUrl;
|
||||
use netpod::HasBackend;
|
||||
use netpod::ProxyConfig;
|
||||
use netpod::APP_CBOR;
|
||||
use netpod::APP_CBOR_FRAMED;
|
||||
use netpod::APP_JSON_FRAMED;
|
||||
use query::api4::events::PlainEventsQuery;
|
||||
|
||||
pub struct EventsHandler {}
|
||||
@@ -44,14 +45,10 @@ impl EventsHandler {
|
||||
if req.method() != Method::GET {
|
||||
Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(body_empty())?)
|
||||
} else {
|
||||
if accepts_cbor_frames(req.headers()) {
|
||||
// self.handle_cbor(req, ctx, proxy_config).await
|
||||
// Ok(crate::proxy::proxy_backend_query::<PlainEventsQuery>(req, ctx, proxy_config).await?)
|
||||
warn!("TODO enabe cbor endpoint");
|
||||
Ok(response_err_msg(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("cbor endpoint currently disabled"),
|
||||
)?)
|
||||
if accepts_cbor_framed(req.headers()) {
|
||||
self.handle_framed(req, APP_CBOR_FRAMED, ctx, proxy_config).await
|
||||
} else if accepts_json_framed(req.headers()) {
|
||||
self.handle_framed(req, APP_JSON_FRAMED, ctx, proxy_config).await
|
||||
} else if accepts_json_or_all(req.headers()) {
|
||||
Ok(crate::proxy::proxy_backend_query::<PlainEventsQuery>(req, ctx, proxy_config).await?)
|
||||
} else {
|
||||
@@ -60,26 +57,33 @@ impl EventsHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_cbor(&self, req: Requ, ctx: &ReqCtx, proxy_config: &ProxyConfig) -> Result<StreamResponse, Error> {
|
||||
async fn handle_framed(
|
||||
&self,
|
||||
req: Requ,
|
||||
accept: &str,
|
||||
ctx: &ReqCtx,
|
||||
proxy_config: &ProxyConfig,
|
||||
) -> Result<StreamResponse, Error> {
|
||||
let (head, _body) = req.into_parts();
|
||||
let url = req_uri_to_url(&head.uri)?;
|
||||
let pairs = get_url_query_pairs(&url);
|
||||
let evq = PlainEventsQuery::from_pairs(&pairs)?;
|
||||
info!("{evq:?}");
|
||||
// Ok(response(StatusCode::INTERNAL_SERVER_ERROR).body(body_empty())?)
|
||||
|
||||
// get_random_query_host_for_backend
|
||||
debug!("{evq:?}");
|
||||
let query_host = get_query_host_for_backend(evq.backend(), proxy_config)?;
|
||||
|
||||
// TODO this ignores the fragment
|
||||
let url_str = format!("{}{}", query_host, head.uri.path_and_query().unwrap());
|
||||
info!("try to contact {url_str}");
|
||||
|
||||
let url_str = format!(
|
||||
"{}{}",
|
||||
query_host,
|
||||
head.uri
|
||||
.path_and_query()
|
||||
.ok_or_else(|| Error::with_msg_no_trace("uri contains no path"))?
|
||||
);
|
||||
debug!("try to contact {url_str}");
|
||||
let uri: Uri = url_str.parse()?;
|
||||
let host = uri.host().ok_or_else(|| Error::with_msg_no_trace("no host in url"))?;
|
||||
let req = Request::builder()
|
||||
.method(Method::GET)
|
||||
.header(header::HOST, uri.host().unwrap())
|
||||
.header(header::ACCEPT, APP_CBOR)
|
||||
.header(header::HOST, host)
|
||||
.header(header::ACCEPT, accept)
|
||||
.header(ctx.header_name(), ctx.header_value())
|
||||
.uri(&uri)
|
||||
.body(body_empty())?;
|
||||
@@ -87,10 +91,10 @@ impl EventsHandler {
|
||||
let res = client.send_request(req).await?;
|
||||
let (head, body) = res.into_parts();
|
||||
if head.status != StatusCode::OK {
|
||||
error!("backend returned error: {head:?}");
|
||||
warn!("backend returned error: {head:?}");
|
||||
Ok(response(StatusCode::INTERNAL_SERVER_ERROR).body(body_empty())?)
|
||||
} else {
|
||||
info!("backend returned OK");
|
||||
debug!("backend returned OK");
|
||||
Ok(response(StatusCode::OK).body(body_stream(StreamIncoming::new(body)))?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,6 +845,11 @@ impl HasTimeout for MapPulseQuery {
|
||||
fn timeout(&self) -> Duration {
|
||||
MAP_PULSE_QUERY_TIMEOUT
|
||||
}
|
||||
|
||||
fn set_timeout(&mut self, timeout: Duration) {
|
||||
// TODO
|
||||
// self.timeout = Some(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromUrl for MapPulseQuery {
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
use httpclient::http::header;
|
||||
use httpclient::http::header::HeaderMap;
|
||||
use netpod::ACCEPT_ALL;
|
||||
use netpod::APP_CBOR_FRAMES;
|
||||
use netpod::APP_CBOR_FRAMED;
|
||||
use netpod::APP_JSON;
|
||||
use netpod::APP_JSON_FRAMED;
|
||||
use netpod::APP_OCTET;
|
||||
|
||||
pub fn accepts_json_or_all(headers: &HeaderMap) -> bool {
|
||||
let accept_def = APP_JSON;
|
||||
let accept = headers
|
||||
.get(header::ACCEPT)
|
||||
.map_or(accept_def, |k| k.to_str().unwrap_or(accept_def));
|
||||
accept.contains(APP_JSON) || accept.contains(ACCEPT_ALL)
|
||||
let h = get_accept_or(APP_JSON, headers);
|
||||
h.contains(APP_JSON) || h.contains(ACCEPT_ALL)
|
||||
}
|
||||
|
||||
pub fn accepts_octets(headers: &HeaderMap) -> bool {
|
||||
let accept_def = "";
|
||||
let accept = headers
|
||||
.get(header::ACCEPT)
|
||||
.map_or(accept_def, |k| k.to_str().unwrap_or(accept_def));
|
||||
accept.contains(APP_OCTET)
|
||||
get_accept_or("", headers).contains(APP_OCTET)
|
||||
}
|
||||
|
||||
pub fn accepts_cbor_frames(headers: &HeaderMap) -> bool {
|
||||
let accept_def = "";
|
||||
let accept = headers
|
||||
.get(header::ACCEPT)
|
||||
.map_or(accept_def, |k| k.to_str().unwrap_or(accept_def));
|
||||
accept.contains(APP_CBOR_FRAMES)
|
||||
pub fn accepts_cbor_framed(headers: &HeaderMap) -> bool {
|
||||
get_accept_or("", headers).contains(APP_CBOR_FRAMED)
|
||||
}
|
||||
|
||||
pub fn accepts_json_framed(headers: &HeaderMap) -> bool {
|
||||
get_accept_or("", headers).contains(APP_JSON_FRAMED)
|
||||
}
|
||||
|
||||
fn get_accept_or<'a>(def: &'a str, headers: &'a HeaderMap) -> &'a str {
|
||||
headers.get(header::ACCEPT).map_or(def, |k| k.to_str().unwrap_or(def))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user