use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use axum::extract::{Path, State};
use axum::http::HeaderMap;
use axum::response::sse::{Event, KeepAlive, Sse};
use axum::response::{IntoResponse, Response};
use dynamic_config::Changes;
use futures_core::Stream;
use crate::document::Document;
use crate::server::{Section, Server, StreamPermit};
use crate::audit::Outcome;
use super::admit::{admit, at_capacity, refuse, served};
const KEEP_ALIVE: Duration = Duration::from_secs(15);
pub(super) async fn stream(
State(server): State<Arc<Server>>,
headers: HeaderMap,
Path((application, profile)): Path<(String, String)>,
) -> Response {
let admitted = match admit(&server, &headers, &application, &profile, "stream") {
Ok(admitted) => admitted,
Err(response) => return *response,
};
if !server.streams_enabled() {
return refuse(
&server,
"stream",
Outcome::NotFound,
Some(admitted.principal.name().to_owned()),
Some((
admitted.section.application().to_owned(),
admitted.section.profile().to_owned(),
)),
);
}
let Some(permit) = server.open_stream() else {
return at_capacity(&server, &admitted);
};
let section = Arc::clone(admitted.section);
let generation = section.generation();
served(&server, &admitted, generation);
let stream = Generations {
application: section.application().to_owned(),
profile: section.profile().to_owned(),
changes: section.changes(),
resume: last_event_id(&headers),
sent: None,
section,
_permit: permit,
};
Sse::new(stream)
.keep_alive(KeepAlive::new().interval(KEEP_ALIVE))
.into_response()
}
fn last_event_id(headers: &HeaderMap) -> Option<u64> {
headers
.get("last-event-id")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.trim().parse::<u64>().ok())
}
struct Generations {
application: String,
profile: String,
section: Arc<Section>,
changes: Changes<Document>,
sent: Option<u64>,
resume: Option<u64>,
_permit: StreamPermit,
}
impl Generations {
fn is_news(&self, generation: u64) -> bool {
if generation == 0 {
return false;
}
match self.sent {
Some(sent) => generation > sent,
None => match self.resume {
Some(resumed) => generation != resumed,
None => true,
},
}
}
fn event(&self, generation: u64) -> Event {
let data = serde_json::json!({
"application": self.application,
"profile": self.profile,
"generation": generation,
})
.to_string();
Event::default()
.id(generation.to_string())
.event("generation")
.data(data)
}
}
impl Stream for Generations {
type Item = Result<Event, std::convert::Infallible>;
fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
let generation = this.section.generation();
if this.is_news(generation) {
this.sent = Some(generation);
return Poll::Ready(Some(Ok(this.event(generation))));
}
let changed = this.changes.changed();
match std::pin::pin!(changed).poll(context) {
Poll::Ready(_) => continue,
Poll::Pending => return Poll::Pending,
}
}
}
}