use std::{
io,
net::Ipv4Addr,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use kynos::{
extract::sse::LastEventId,
middleware::compression::{Compression, streaming::LatencyMode},
prelude::*,
response::stream::sse::{Event, KeepAlive, Sse},
server::Server,
};
use serde::{Deserialize, Serialize};
#[derive(Schema, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum Update {
Reading {
at_millis: i64,
value: f64,
},
Alert {
threshold: String,
},
Closed,
}
impl Update {
fn event_name(&self) -> &'static str {
match self {
Self::Reading { .. } => "reading",
Self::Alert { .. } => "alert",
Self::Closed => "closed",
}
}
}
struct Feed {
next_id: u64,
remaining: u32,
advised: bool,
}
impl Feed {
fn resuming_after(last_event_id: Option<&str>) -> Self {
let next_id = last_event_id
.and_then(|id| id.parse::<u64>().ok())
.map_or(0, |id| id + 1);
Self {
next_id,
remaining: 10,
advised: false,
}
}
}
impl futures_core::Stream for Feed {
type Item = Result<Event<Update>, io::Error>;
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.remaining == 0 {
return Poll::Ready(None);
}
let id = self.next_id;
self.next_id += 1;
self.remaining -= 1;
let update = if self.remaining == 0 {
Update::Closed
} else if id % 5 == 4 {
Update::Alert {
threshold: "high".to_owned(),
}
} else {
Update::Reading {
at_millis: i64::try_from(id).unwrap_or(i64::MAX),
value: f64::from(u32::try_from(id).unwrap_or(u32::MAX)),
}
};
let name = update.event_name();
let mut event = Event::new(update).event(name).id(id.to_string());
if !self.advised {
self.advised = true;
event = event
.retry(2_000)
.comment("reconnect after two seconds, resuming from the last id");
}
Poll::Ready(Some(Ok(event)))
}
}
#[kynos::get("/updates")]
async fn updates(LastEventId(resume): LastEventId) -> Sse<Feed> {
Sse::new(Feed::resuming_after(resume.as_deref())).keep_alive(
KeepAlive::new()
.interval(Duration::from_secs(15))
.comment("still connected"),
)
}
#[kynos::get("/updates/burst")]
async fn burst() -> Sse<Feed> {
Sse::new(Feed::resuming_after(None))
}
#[tokio::main]
async fn main() -> kynos::Result<()> {
let router = Router::<()>::new()
.mount(kynos::routes![updates, burst])
.intercept(Compression::new().latency_mode(LatencyMode::Interactive));
let document = router.openapi()?;
println!("{}", document.to_json()?);
Server::new(router.build(())?)
.bind((Ipv4Addr::UNSPECIFIED, 3000))
.serve()
.await
}