use std::sync::Arc;
use bytes::Bytes;
use http_body_util::BodyExt;
use hyper::{Response, StatusCode};
use zygo_core::supervisor::Request as Control;
use zygo_core::supervisor::client::Client;
use super::reply::{ApiBody, HttpError, reply_to_json};
use super::usage::count_usage;
use super::{Api, MAX_IDLE_CLIENTS};
struct Streamed(tokio::sync::mpsc::Receiver<Bytes>);
impl hyper::body::Body for Streamed {
type Data = Bytes;
type Error = std::io::Error;
fn poll_frame(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<std::result::Result<hyper::body::Frame<Bytes>, Self::Error>>> {
self.0
.poll_recv(cx)
.map(|frame| frame.map(|bytes| Ok(hyper::body::Frame::data(bytes))))
}
}
const STREAM_CONTENT_TYPE: &str = "application/x-ndjson";
const STREAM_BACKLOG: usize = 64;
pub(super) async fn exec_streaming(
api: &Arc<Api>,
request: Control,
) -> Result<Response<ApiBody>, HttpError> {
let (lines, rx) = tokio::sync::mpsc::channel::<Bytes>(STREAM_BACKLOG);
let api = Arc::clone(api);
tokio::task::spawn_blocking(move || {
let mut client = match api.clients.lock().expect("clients").pop() {
Some(client) => client,
None => match Client::connect_or_start(&api.paths, &api.exe) {
Ok(client) => client,
Err(e) => {
let _ = lines.blocking_send(line(&serde_json::json!({
"status": 500,
"error": format!("{e:#}"),
})));
return;
}
},
};
let answer = client.send_streaming(&request, |stream, data| {
let _ = lines.blocking_send(line(&serde_json::json!({
"stream": stream.as_str(),
"data": data,
})));
});
let last = match answer {
Ok(reply) => {
count_usage(&api, &reply);
let (status, mut body) = reply_to_json(reply);
body["status"] = status.as_u16().into();
body
}
Err(e) => serde_json::json!({ "status": 500, "error": format!("{e:#}") }),
};
let _ = lines.blocking_send(line(&last));
let mut idle = api.clients.lock().expect("clients");
if idle.len() < MAX_IDLE_CLIENTS {
idle.push(client);
}
});
let body = Streamed(rx).boxed();
Response::builder()
.status(StatusCode::OK)
.header("content-type", STREAM_CONTENT_TYPE)
.header("cache-control", "no-store")
.header("x-accel-buffering", "no")
.body(body)
.map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e))
}
fn line(value: &serde_json::Value) -> Bytes {
let mut out = serde_json::to_vec(value).unwrap_or_else(|_| b"{}".to_vec());
out.push(b'\n');
Bytes::from(out)
}